Skip to content

Commit d2046f6

Browse files
fix: iterative evm call execution (#713)
- Returning iterative execution into EVM for complex calls - Capped number of iterations by arbitrary number 50 to avoid infinite loops
1 parent 5262926 commit d2046f6

File tree

2 files changed

+74
-44
lines changed

2 files changed

+74
-44
lines changed

ethereum/src/evm.rs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,33 +57,48 @@ impl<E: ExecutionProvider<Ethereum>> EthereumEvm<E> {
5757
let mut db = ProofDB::new(self.block_id, self.execution.clone());
5858
_ = db.state.prefetch_state(tx, validate_tx).await;
5959

60-
// Pre-fetch all required state
61-
loop {
62-
if !db.state.needs_update() {
63-
break;
60+
// Iterative execution with state fetching
61+
let mut iteration = 0;
62+
const MAX_ITERATIONS: u32 = 50; // Prevent infinite loops
63+
64+
let tx_res = loop {
65+
iteration += 1;
66+
if iteration > MAX_ITERATIONS {
67+
return Err(EvmError::Generic(
68+
"Maximum iterations reached while fetching state".to_string(),
69+
));
70+
}
71+
72+
// Update state first if needed
73+
if db.state.needs_update() {
74+
debug!(
75+
"evm cache miss (iteration {}): {:?}",
76+
iteration,
77+
db.state.access.as_ref().unwrap()
78+
);
79+
db.state
80+
.update_state()
81+
.await
82+
.map_err(|e| EvmError::Generic(e.to_string()))?;
6483
}
65-
debug!("evm cache miss: {:?}", db.state.access.as_ref().unwrap());
66-
db.state
67-
.update_state()
68-
.await
69-
.map_err(|e| EvmError::Generic(e.to_string()))?;
70-
}
7184

72-
// Now execute synchronously with all state loaded
73-
let context = self.get_context(tx, self.block_id, validate_tx).await?;
85+
// Create EVM after any async operations
86+
let context = self.get_context(tx, self.block_id, validate_tx).await?;
7487

75-
// Execute in a scope to ensure EVM is dropped before any async operations
76-
let (result, accounts) = {
77-
let mut evm = context.with_db(db).build_mainnet();
78-
let res = evm.replay();
79-
let db = evm.db_mut();
80-
let accounts = mem::take(&mut db.state.accounts);
81-
(res, accounts)
88+
// Execute in a scope to ensure EVM is dropped before any potential async operations
89+
let (result, needs_update) = {
90+
let mut evm = context.with_db(&mut db).build_mainnet();
91+
let res = evm.replay();
92+
let needs_update = evm.db_mut().state.needs_update();
93+
(res, needs_update)
94+
};
95+
96+
if result.is_ok() || !needs_update {
97+
break result.map(|res| (res.result, mem::take(&mut db.state.accounts)));
98+
}
8299
};
83100

84-
result
85-
.map(|r| (r.result, accounts))
86-
.map_err(|err| EvmError::Generic(format!("generic: {err}")))
101+
tx_res.map_err(|err| EvmError::Generic(format!("generic: {err}")))
87102
}
88103

89104
async fn get_context(

opstack/src/evm.rs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,33 +61,48 @@ impl<E: ExecutionProvider<OpStack>> OpStackEvm<E> {
6161
let mut db = ProofDB::new(self.block_id, self.execution.clone());
6262
_ = db.state.prefetch_state(tx, validate_tx).await;
6363

64-
// Pre-fetch all required state
65-
loop {
66-
if !db.state.needs_update() {
67-
break;
64+
// Iterative execution with state fetching
65+
let mut iteration = 0;
66+
const MAX_ITERATIONS: u32 = 50; // Prevent infinite loops
67+
68+
let tx_res = loop {
69+
iteration += 1;
70+
if iteration > MAX_ITERATIONS {
71+
return Err(EvmError::Generic(
72+
"Maximum iterations reached while fetching state".to_string(),
73+
));
74+
}
75+
76+
// Update state first if needed
77+
if db.state.needs_update() {
78+
debug!(
79+
"evm cache miss (iteration {}): {:?}",
80+
iteration,
81+
db.state.access.as_ref().unwrap()
82+
);
83+
db.state
84+
.update_state()
85+
.await
86+
.map_err(|e| EvmError::Generic(e.to_string()))?;
6887
}
69-
debug!("evm cache miss: {:?}", db.state.access.as_ref().unwrap());
70-
db.state
71-
.update_state()
72-
.await
73-
.map_err(|e| EvmError::Generic(e.to_string()))?;
74-
}
7588

76-
// Now execute synchronously with all state loaded
77-
let context = self.get_context(tx, self.block_id, validate_tx).await?;
89+
// Create EVM after any async operations
90+
let context = self.get_context(tx, self.block_id, validate_tx).await?;
7891

79-
// Execute in a scope to ensure EVM is dropped before any async operations
80-
let (result, accounts) = {
81-
let mut evm = context.with_db(db).build_op();
82-
let res = evm.replay();
83-
let db = evm.0.db_mut();
84-
let accounts = mem::take(&mut db.state.accounts);
85-
(res, accounts)
92+
// Execute in a scope to ensure EVM is dropped before any potential async operations
93+
let (result, needs_update) = {
94+
let mut evm = context.with_db(&mut db).build_op();
95+
let res = evm.replay();
96+
let needs_update = evm.0.db_mut().state.needs_update();
97+
(res, needs_update)
98+
};
99+
100+
if result.is_ok() || !needs_update {
101+
break result.map(|res| (res.result, mem::take(&mut db.state.accounts)));
102+
}
86103
};
87104

88-
result
89-
.map(|r| (r.result, accounts))
90-
.map_err(|err| EvmError::Generic(format!("generic: {err}")))
105+
tx_res.map_err(|err| EvmError::Generic(format!("generic: {err}")))
91106
}
92107

93108
async fn get_context(

0 commit comments

Comments
 (0)