Skip to content
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

feat(levm): add hooks support and implement DefaultHook #1869

Merged
merged 32 commits into from
Feb 7, 2025

Conversation

lima-limon-inc
Copy link
Contributor

@lima-limon-inc lima-limon-inc commented Feb 3, 2025

Motivation

LEVM neeeds to support a L2 feature called "Privilege Transactions". To achieve this, some functionality of the EVM has to be able to be customized. In order to achieve this, the EVM will start to support "Hooks" which will modify the prepare_execution and finalize_execution functions of the EVM.

Description
This PR implements the DefaultHook, which is a hook that maintains backwards compatibility with LEVM's previous way of operation; whilst allowing the introduction of future alternative hooks.

In order to achieve this, the following changes were made:

  • Move eip7702_set_access_code to a standalone function.
    • Move access_account to an standalone function.
  • Move add_intrinsic_gas to a standalone function .
  • Make beacon_root_contract_call_levm receive a Arc<dyn LevmDatabase> instead of a Arc<StoreWrapper> to make it more like execute_tx_levm.
  • Replace &Arch with simply Arc in utils.rs.
  • Implement DefaultHook::prepare_execution.
  • Implement DefaultHook::finalize_execution.

Closes #1879
Closes #1880
Closes #1629

Copy link

github-actions bot commented Feb 3, 2025

| File                                                                              | Lines | Diff |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/cmd/ef_tests/levm/deserialize.rs                  | 371   | -34  |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/cmd/ef_tests/levm/report.rs                       | 847   | -64  |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/cmd/ef_tests/levm/runner/levm_runner.rs           | 398   | -20  |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/cmd/ef_tests/levm/runner/revm_runner.rs           | 477   | -36  |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/cmd/ef_tests/levm/types.rs                        | 332   | +64  |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/cmd/ethrex/cli.rs                                 | 132   | -8   |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/common/types/transaction.rs                | 2546  | -3   |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/networking/p2p/discv4/lookup.rs            | 306   | +1   |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/networking/p2p/kademlia.rs                 | 492   | +1   |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/networking/p2p/net.rs                      | 154   | +154 |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/execution_handlers.rs          | 243   | +5   |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/hooks/default_hook.rs          | 295   | +295 |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/hooks/hook.rs                  | 18    | +18  |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/hooks/mod.rs                   | 2     | +2   |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/lib.rs                         | 19    | +1   |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/opcode_handlers/environment.rs | 361   | +20  |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/opcode_handlers/system.rs      | 690   | +41  |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/utils.rs                       | 550   | +116 |
+-----------------------------------------------------------------------------------+-------+------+
| /home/runner/work/ethrex/ethrex/crates/vm/levm/src/vm.rs                          | 419   | -337 |
+-----------------------------------------------------------------------------------+-------+------+

Total lines added: +718
Total lines removed: 502
Total lines changed: 1220

Copy link

github-actions bot commented Feb 3, 2025

Benchmark Results Comparison

PR Results

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Factorial 233.2 ± 0.7 231.8 234.3 1.00
levm_Factorial 910.6 ± 11.4 894.3 923.5 3.90 ± 0.05

Benchmark Results: Factorial - Recursive

Command Mean [s] Min [s] Max [s] Relative
revm_FactorialRecursive 1.492 ± 0.075 1.366 1.583 1.00
levm_FactorialRecursive 15.710 ± 0.019 15.675 15.741 10.53 ± 0.53

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Fibonacci 205.9 ± 0.8 204.7 207.6 1.00
levm_Fibonacci 905.1 ± 7.8 896.6 917.6 4.40 ± 0.04

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ManyHashes 8.6 ± 0.0 8.6 8.7 1.00
levm_ManyHashes 18.2 ± 0.2 17.9 18.6 2.12 ± 0.03

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
revm_BubbleSort 3.221 ± 0.023 3.193 3.275 1.00
levm_BubbleSort 6.201 ± 0.083 6.149 6.429 1.93 ± 0.03

Benchmark Results: ERC20 - Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Transfer 252.7 ± 3.1 247.3 256.3 1.00
levm_ERC20Transfer 550.2 ± 2.9 547.2 557.3 2.18 ± 0.03

Benchmark Results: ERC20 - Mint

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Mint 141.7 ± 1.0 140.7 144.3 1.00
levm_ERC20Mint 364.5 ± 5.0 359.3 377.0 2.57 ± 0.04

Benchmark Results: ERC20 - Approval

Command Mean [s] Min [s] Max [s] Relative
revm_ERC20Approval 1.040 ± 0.004 1.034 1.045 1.00
levm_ERC20Approval 2.054 ± 0.019 2.034 2.092 1.98 ± 0.02

Main Results

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Factorial 234.1 ± 1.7 232.0 238.1 1.00
levm_Factorial 897.3 ± 12.0 889.3 930.5 3.83 ± 0.06

Benchmark Results: Factorial - Recursive

Command Mean [s] Min [s] Max [s] Relative
revm_FactorialRecursive 1.454 ± 0.075 1.347 1.553 1.00
levm_FactorialRecursive 15.593 ± 0.022 15.561 15.637 10.72 ± 0.56

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Fibonacci 203.1 ± 7.0 200.3 222.9 1.00
levm_Fibonacci 892.4 ± 11.8 881.2 907.3 4.39 ± 0.16

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 18.1 ± 0.1 17.9 18.3 2.11 ± 0.02

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
revm_BubbleSort 3.212 ± 0.017 3.192 3.237 1.00
levm_BubbleSort 6.107 ± 0.044 6.063 6.191 1.90 ± 0.02

Benchmark Results: ERC20 - Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Transfer 246.4 ± 1.2 245.1 248.5 1.00
levm_ERC20Transfer 541.8 ± 5.1 538.1 555.7 2.20 ± 0.02

Benchmark Results: ERC20 - Mint

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Mint 141.9 ± 0.5 141.2 142.9 1.00
levm_ERC20Mint 350.6 ± 1.8 348.8 354.3 2.47 ± 0.02

Benchmark Results: ERC20 - Approval

Command Mean [s] Min [s] Max [s] Relative
revm_ERC20Approval 1.034 ± 0.010 1.027 1.054 1.00
levm_ERC20Approval 2.045 ± 0.064 2.011 2.225 1.98 ± 0.07

@lima-limon-inc
Copy link
Contributor Author

lima-limon-inc commented Feb 3, 2025

Notes/suggestion (simply ideas):

  • Move eip7702_set_access_code to a standalone function.
    • Move access_account to an standalone function.
  • Move add_intrinsic_gas to a standalone function
  • Move acces list and authorization list to Environment
  • Remove & to Arc in utils.rs
  • Implement prepare_execution default hook
  • Implement post_execution default hook

Copy link

github-actions bot commented Feb 4, 2025

@lima-limon-inc lima-limon-inc force-pushed the feat/hooks branch 2 times, most recently from 7e1c913 to ec41b03 Compare February 4, 2025 13:22
@lima-limon-inc lima-limon-inc self-assigned this Feb 4, 2025
@lima-limon-inc lima-limon-inc added the levm Lambda EVM implementation label Feb 4, 2025
@lima-limon-inc lima-limon-inc changed the title feat(levm): add hooks support feat(levm): add hooks support and implement DefaultHook::prepare_execution Feb 6, 2025
@lima-limon-inc lima-limon-inc marked this pull request as ready for review February 6, 2025 13:37
@lima-limon-inc lima-limon-inc requested a review from a team as a code owner February 6, 2025 13:37
Comment on lines 163 to 171
pub fn new(env: Environment, db: Arc<dyn Database>, cache: CacheDB) -> Result<Self, VMError> {
let default_hook: Box<dyn Hook> = Box::new(DefaultHook::new());
let hooks = vec![default_hook];
Ok(Self {
db,
env,
cache,
hooks,
})
Copy link
Contributor

Choose a reason for hiding this comment

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

If the checks are made in the Environment, should we change the new to not return a Result type?

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 totally right Tomi!

Updated here: 8e28a39

db: Arc<dyn Database>,
value: U256,
) -> Result<Self, VMError> {
let to = TxKind::Call(Address::from_low_u64_be(42));
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is there a fix number? Maybe defining a constant should be better

Copy link
Contributor Author

@lima-limon-inc lima-limon-inc Feb 6, 2025

Choose a reason for hiding this comment

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

I saw that in tests.rs 42 was used everywhere. I don't know why 42 specifically, although I have my theories.

However, that can certainly lead to unwanted bugs. I'll change that so that it receives the to as an argument and I'll also add a comment indicating that it is only intended for testing.

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: f9f7a17

Comment on lines 108 to 136
cache: &mut CacheDB,
db: Arc<dyn Database>,
value: U256,
calldata: Bytes,
origin: Address,
tx_kind: TxKind,
refunded_gas: u64,
gas_limit: u64,
config: EVMConfig,
block_number: U256,
coinbase: Address,
timestamp: U256,
prev_randao: Option<H256>,
chain_id: U256,
base_fee_per_gas: U256,
gas_price: U256, // Effective gas price
block_excess_blob_gas: Option<U256>,
block_blob_gas_used: Option<U256>,
tx_blob_hashes: Vec<H256>,
tx_max_priority_fee_per_gas: Option<U256>,
tx_max_fee_per_gas: Option<U256>,
tx_max_fee_per_blob_gas: Option<U256>,
tx_nonce: u64,
block_gas_limit: u64,
transient_storage: TransientStorage,
access_list: AccessList,
authorization_list: Option<AuthorizationList>,
call_frames: Vec<CallFrame>,
) -> Result<Environment, VMError> {
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 maybe using a struct Transaction containing all the transaction related fields should be better. All this values shouldn't change in the execution.
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.

I think you are totally right! As a matter of fact, I wanted to do that exact same thing (see comment: #1869 (comment)).

I think I simply forgot to do it, I was a big PR as it is. I'll change it.

Thank you Tomi.

Copy link
Contributor

@fborello-lambda fborello-lambda Feb 6, 2025

Choose a reason for hiding this comment

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

I think a struct is needed, if we can use the GenericTransaction from ethrex/core it would be awesome. However, if we have to implement a new struct only to be used by levm or if he integration with GenericTransaction isn't that obvious, we could create an issue and do it in another PR.

Copy link
Contributor

@fborello-lambda fborello-lambda left a comment

Choose a reason for hiding this comment

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

Amazing!! left 2 minor suggestions

Comment on lines 163 to 319
pub fn new(env: Environment, db: Arc<dyn Database>, cache: CacheDB) -> Self {
let default_hook: Box<dyn Hook> = Box::new(DefaultHook::new());
let hooks = vec![default_hook];
Self {
db,
env,
cache,
hooks,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, in the future, a method for the VM called append_hook could be handy.
So we can do something like:

VM::new(db, env, cache).append_hook(custom_logic_hook)

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, left some comments

@lima-limon-inc lima-limon-inc marked this pull request as draft February 6, 2025 14:38
@lima-limon-inc
Copy link
Contributor Author

What do you think of the new directory structure?

@lima-limon-inc lima-limon-inc marked this pull request as ready for review February 6, 2025 18:58
Comment on lines 20 to 22
// NOTE: I'm leaving this in case there needs to be a custom new()
// function when the post_execution_changes methods is
// implemented.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure it makes a lot of sense. Either we need a custom new that will take parameters and we'll need to make changes anyway, or we don't need it and we just have a redundant way to construct this.
Also, whenever we leave some constructor method "just in case", I recommend we go for a builder pattern, which is much better for future-proofing an interface than trying to predict the parameters a constructor might have in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great point @Oppen; I'll remove it ASAP

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done here: 8fc1496

I also made it a unit struct, I think it makes more sense like that. @tomip01 also pointed it out.

@@ -166,6 +168,7 @@ pub struct VM {
pub tx_kind: TxKind,
pub access_list: AccessList,
pub authorization_list: Option<AuthorizationList>,
pub hooks: Vec<Rc<dyn Hook>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be Arc. Otherwise, db being Arc would be pointless, given this makes the whole thing not Send.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with the above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted! Thanks for the feedback.

Changed here: aa1c1b8

// NOTE: ATTOW the default hook is created in VM::new(), so
// (in theory) _at least_ the default prepare execution should
// run
for hook in self.hooks.clone() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this should be enough:

Suggested change
for hook in self.hooks.clone() {
for hook in self.hooks.iter() {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I'm getting the following error:

402 | ...or hook in self.hooks.iter() {
    |               -----------------
    |               |
    |               immutable borrow occurs here
    |               immutable borrow later used here
403 | ...   hook.prepare_execution(self, &mut initial_call_frame...
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

@@ -822,7 +428,9 @@ impl VM {

report.gas_used = self.gas_used(&initial_call_frame, &report)?;

self.finalize_execution(&initial_call_frame, &mut report)?;
for hook in self.hooks.clone() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same:

Suggested change
for hook in self.hooks.clone() {
for hook in self.hooks.iter() {

@@ -928,119 +522,11 @@ impl VM {
output: Bytes::new(),
};

self.finalize_execution(initial_call_frame, &mut report)?;
for hook in self.hooks.clone() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for hook in self.hooks.clone() {
for hook in self.hooks.iter() {

@@ -166,6 +168,7 @@ pub struct VM {
pub tx_kind: TxKind,
pub access_list: AccessList,
pub authorization_list: Option<AuthorizationList>,
pub hooks: Vec<Rc<dyn Hook>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with the above.

pub fn execute(&mut self) -> Result<ExecutionReport, VMError> {
let mut initial_call_frame = self
.call_frames
.pop()
.ok_or(VMError::Internal(InternalError::CouldNotPopCallframe))?;

self.prepare_execution(&mut initial_call_frame)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep this function, as it serves to keep the VM's main function very easy to understand high-level.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted! Great point. I'll change it ASAP

Copy link
Contributor Author

@lima-limon-inc lima-limon-inc Feb 7, 2025

Choose a reason for hiding this comment

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

Changed here: 62261a6

@@ -822,7 +428,9 @@ impl VM {

report.gas_used = self.gas_used(&initial_call_frame, &report)?;

self.finalize_execution(&initial_call_frame, &mut report)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto

@@ -928,119 +522,11 @@ impl VM {
output: Bytes::new(),
};

self.finalize_execution(initial_call_frame, &mut report)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto

@lima-limon-inc
Copy link
Contributor Author

I reverted the last 3 commits because the Hive tests were failing, I want to check which commit broke it.

Copy link

github-actions bot commented Feb 7, 2025

Summary: 9450/14408 (65.59%)

Prague: 2373/2373 (100.00%)
Cancun: 3579/3579 (100.00%)
Shanghai: 221/221 (100.00%)
Paris: 62/62 (100.00%)
London: 39/39 (100.00%)
Berlin: 35/35 (100.00%)
Istanbul: 34/35 (97.14%)
Constantinople: 1041/2439 (42.68%)
Byzantium: 1000/2330 (42.92%)
Homestead: 585/1324 (44.18%)
Frontier: 141/742 (19.00%)

@lima-limon-inc lima-limon-inc changed the title feat(levm): add hooks support and implement DefaultHook::prepare_execution feat(levm): add hooks support and implement DefaultHook Feb 7, 2025
@lima-limon-inc lima-limon-inc added this pull request to the merge queue Feb 7, 2025
Merged via the queue into main with commit 8804c4d Feb 7, 2025
24 checks passed
@lima-limon-inc lima-limon-inc deleted the feat/hooks branch February 7, 2025 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
levm Lambda EVM implementation
Projects
None yet
6 participants