diff --git a/Cargo.lock b/Cargo.lock index 5644b97828..7cd1f97892 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4257,6 +4257,7 @@ dependencies = [ "ark-bn254", "ark-ec", "ark-ff 0.5.0", + "bitvec", "bls12_381 0.8.0", "bytes", "colored", diff --git a/crates/l2/prover/src/guest_program/src/risc0/Cargo.lock b/crates/l2/prover/src/guest_program/src/risc0/Cargo.lock index 9c44ce4298..3d040a2ddf 100644 --- a/crates/l2/prover/src/guest_program/src/risc0/Cargo.lock +++ b/crates/l2/prover/src/guest_program/src/risc0/Cargo.lock @@ -1209,6 +1209,7 @@ dependencies = [ "ark-bn254", "ark-ec", "ark-ff", + "bitvec", "bls12_381", "bytes", "datatest-stable", diff --git a/crates/l2/prover/src/guest_program/src/sp1/Cargo.lock b/crates/l2/prover/src/guest_program/src/sp1/Cargo.lock index a15233f254..275801fc33 100644 --- a/crates/l2/prover/src/guest_program/src/sp1/Cargo.lock +++ b/crates/l2/prover/src/guest_program/src/sp1/Cargo.lock @@ -973,6 +973,7 @@ dependencies = [ "ark-bn254", "ark-ec", "ark-ff", + "bitvec", "bls12_381", "bytes", "datatest-stable", diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index ec81c64064..eb18609445 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -21,7 +21,11 @@ sha3 = "0.10.8" datatest-stable = "0.2.9" walkdir = "2.5.0" secp256k1.workspace = true -p256 = { version = "0.13.2", features = ["ecdsa", "arithmetic", "expose-field"] } +p256 = { version = "0.13.2", features = [ + "ecdsa", + "arithmetic", + "expose-field", +] } sha2 = "0.10.8" ripemd = "0.1.3" malachite = "0.6.1" @@ -39,6 +43,7 @@ ark-ff = "0.5.0" strum = { version = "0.27.1", features = ["derive"] } k256 = "0.13.4" +bitvec = { version = "1.0.1", features = ["alloc"] } [dev-dependencies] hex.workspace = true diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index bb50a3fcf8..18673b48d0 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -14,7 +14,8 @@ use crate::{ vm::{Substate, VM}, }; use ExceptionalHalt::OutOfGas; -use bytes::{Bytes, buf::IntoIter}; +use bitvec::{bitvec, order::Msb0, vec::BitVec}; +use bytes::Bytes; use ethrex_common::{ Address, H256, U256, evm::calculate_create_address, @@ -29,7 +30,7 @@ use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, }; use sha3::{Digest, Keccak256}; -use std::{collections::HashMap, iter::Enumerate}; +use std::collections::HashMap; pub type Storage = HashMap; // ================== Address related functions ====================== @@ -79,59 +80,57 @@ pub fn calculate_create2_address( /// offsets of bytes `0x5B` (`JUMPDEST`) within push constants. #[derive(Debug)] pub struct JumpTargetFilter { - /// The list of invalid jump target offsets. - filter: Vec, - /// The last processed offset, plus one. - offset: usize, - - /// Program bytecode iterator. - iter: Enumerate>, - /// Number of bytes remaining to process from the last push instruction. - partial: usize, + bytecode: Bytes, + jumpdests: Option>, } impl JumpTargetFilter { /// Create an empty `JumpTargetFilter`. pub fn new(bytecode: Bytes) -> Self { Self { - filter: Vec::new(), - offset: 0, - - iter: bytecode.into_iter().enumerate(), - partial: 0, + bytecode, + jumpdests: None, } } /// Check whether a target jump address is blacklisted or not. /// - /// This method may potentially grow the filter if the requested address is out of range. + /// Builds the jumpdest table on the first call, and caches it for future calls. + #[expect( + clippy::as_conversions, + clippy::arithmetic_side_effects, + clippy::indexing_slicing + )] pub fn is_blacklisted(&mut self, address: usize) -> bool { - if let Some(delta) = address.checked_sub(self.offset) { - // It is not realistic to expect a bytecode offset to overflow an `usize`. - #[expect(clippy::arithmetic_side_effects)] - for (offset, value) in (&mut self.iter).take(delta + 1) { - match self.partial.checked_sub(1) { - None => { - // Neither the `as` conversions nor the subtraction can fail here. - #[expect(clippy::as_conversions)] - if (Opcode::PUSH1..=Opcode::PUSH32).contains(&Opcode::from(value)) { - self.partial = value as usize - Opcode::PUSH0 as usize; - } - } - Some(partial) => { - self.partial = partial; - - #[expect(clippy::as_conversions)] - if value == Opcode::JUMPDEST as u8 { - self.filter.push(offset); - } + match self.jumpdests { + // Already built the jumpdest table, just check it + Some(ref jumpdests) => address >= jumpdests.len() || !jumpdests[address], + // First time we are called, need to build the jumpdest table + None => { + let code = &self.bytecode; + let len = code.len(); + let mut jumpdests = bitvec![u8, Msb0; 0; len]; // All false, size = len + + let mut i = 0; + while i < len { + + let opcode = Opcode::from(code[i]); + if opcode == Opcode::JUMPDEST { + jumpdests.set(i, true); + } else if (Opcode::PUSH1..=Opcode::PUSH32).contains(&opcode) { + // PUSH1 (0x60) to PUSH32 (0x7f): skip 1 to 32 bytes + let skip = opcode as usize - Opcode::PUSH0 as usize; + i += skip; // Advance past data bytes } + i += 1; } - } - self.filter.last() == Some(&address) - } else { - self.filter.binary_search(&address).is_ok() + let is_blacklisted = address >= jumpdests.len() || !jumpdests[address]; + + self.jumpdests = Some(jumpdests); + + is_blacklisted + } } } }