-
Notifications
You must be signed in to change notification settings - Fork 16
chore: implement simulated execution for bundles in Executable::simulate
#74
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
3a0d463
1f8da66
8e15f37
cf780f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -275,7 +275,7 @@ impl<P: Platform> Executable<P> { | |||||||||||||||||||
| DB: DatabaseRef<Error = ProviderError> + Debug, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| match self { | ||||||||||||||||||||
| Self::Bundle(_) => unreachable!("asd"), | ||||||||||||||||||||
| Self::Bundle(bundle) => Self::simulate_bundle(bundle, block, db), | ||||||||||||||||||||
| Self::Transaction(tx) => Self::simulate_transaction(tx, block, db) | ||||||||||||||||||||
| .map_err(ExecutionError::InvalidTransaction), | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
@@ -312,6 +312,110 @@ impl<P: Platform> Executable<P> { | |||||||||||||||||||
| state: BundleState::default(), | ||||||||||||||||||||
| }) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// Simulates a bundle of transactions and returns the simulated execution | ||||||||||||||||||||
| /// outcome of all transactions in the bundle. No state changes are | ||||||||||||||||||||
| /// persisted. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// Notes: | ||||||||||||||||||||
| /// - Bundles that are not eligible for execution in the current block are | ||||||||||||||||||||
| /// considered invalid, and no execution result will be produced. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// - All transactions in the bundle are simulated in the order in which they | ||||||||||||||||||||
| /// were defined in the bundle. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// - Each transaction is simulated on the in-memory state produced by the | ||||||||||||||||||||
| /// previous transaction in the bundle, but changes are not committed or | ||||||||||||||||||||
| /// persisted. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// - Transactions that cause EVM errors will invalidate the bundle, and no | ||||||||||||||||||||
| /// execution result will be produced. Bundle transactions can be marked | ||||||||||||||||||||
| /// optional [`Bundle::is_optional`], and invalid outcomes are handled by | ||||||||||||||||||||
| /// discarding them. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// - Transactions that fail gracefully (revert or halt) and are not optional | ||||||||||||||||||||
| /// will invalidate the bundle, and no execution result will be produced. | ||||||||||||||||||||
| /// Bundle transactions can be marked as allowed to fail | ||||||||||||||||||||
| /// [`Bundle::is_allowed_to_fail`], and failure outcomes are handled by | ||||||||||||||||||||
| /// including them if allowed. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// See truth table (same as execute_bundle): | ||||||||||||||||||||
| /// | success | `allowed_to_fail` | optional | Action | | ||||||||||||||||||||
| /// | ------: | :---------------: | :------: | :------ | | ||||||||||||||||||||
| /// | true | *don’t care* | *any* | include | | ||||||||||||||||||||
| /// | false | true | *any* | include | | ||||||||||||||||||||
| /// | false | false | true | discard | | ||||||||||||||||||||
| /// | false | false | false | error | | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// - Post-execution validation is skipped for simulation, as no state changes | ||||||||||||||||||||
| /// are persisted. | ||||||||||||||||||||
| fn simulate_bundle<DB>( | ||||||||||||||||||||
| bundle: types::Bundle<P>, | ||||||||||||||||||||
| block: &BlockContext<P>, | ||||||||||||||||||||
| db: &DB, | ||||||||||||||||||||
| ) -> Result<ExecutionResult<P>, ExecutionError<P>> | ||||||||||||||||||||
| where | ||||||||||||||||||||
| DB: DatabaseRef<Error = ProviderError> + Debug, | ||||||||||||||||||||
| { | ||||||||||||||||||||
| let eligible = bundle.is_eligible(block); | ||||||||||||||||||||
| if !eligible { | ||||||||||||||||||||
| return Err(ExecutionError::IneligibleBundle(eligible)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let evm_env = block.evm_env(); | ||||||||||||||||||||
| let evm_config = block.evm_config(); | ||||||||||||||||||||
| let mut state = State::builder().with_database(WrapDatabaseRef(db)).build(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let mut discarded = Vec::new(); | ||||||||||||||||||||
| let mut results = Vec::with_capacity(bundle.transactions().len()); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| for transaction in bundle.transactions_encoded() { | ||||||||||||||||||||
| let tx_hash = *transaction.tx_hash(); | ||||||||||||||||||||
| let optional = bundle.is_optional(&tx_hash); | ||||||||||||||||||||
| let allowed_to_fail = bundle.is_allowed_to_fail(&tx_hash); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let result = evm_config | ||||||||||||||||||||
| .evm_with_env(&mut state, evm_env.clone()) | ||||||||||||||||||||
| .transact(&transaction); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| match result { | ||||||||||||||||||||
| // Valid transaction or allowed to fail: include it in the bundle | ||||||||||||||||||||
| Ok(ExecResultAndState { result, .. }) | ||||||||||||||||||||
| if result.is_success() || allowed_to_fail => | ||||||||||||||||||||
| { | ||||||||||||||||||||
| results.push(result); | ||||||||||||||||||||
|
||||||||||||||||||||
| Ok(ExecResultAndState { result, .. }) | |
| if result.is_success() || allowed_to_fail => | |
| { | |
| results.push(result); | |
| Ok(ExecResultAndState { result, state: tx_state }) | |
| if result.is_success() || allowed_to_fail => | |
| { | |
| results.push(result); | |
| state.commit(tx_state); // Commit to allow next tx to see changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you should do db.commit
But you shouldn't do merge_transitions
Outdated
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misleading comment about state commitment
The comment says "No db.commit(state) for simulation" but the variable is named state, not db. This is inconsistent with the actual code and could be confusing.
Additionally, this comment is misleading because state changes should be committed between transactions (as noted in the bug comment above). The comment should clarify that:
- State changes ARE committed in-memory between transactions (via
state.commit(tx_state)) - The final state is not returned (line 416 returns
BundleState::default())
Consider revising to: "State changes committed in-memory but not returned (simulation)"
| // Note: No db.commit(state) for simulation | |
| // State changes committed in-memory but not returned (simulation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation Inaccuracy: State changes ARE persisted in simulation
The documentation states "Each transaction is simulated on the in-memory state produced by the previous transaction in the bundle, but changes are not committed or persisted" (lines 327-329). However, this is misleading.
While the final state is not returned (line 416 returns
BundleState::default()), the in-memory state changes should still be committed within theStateobject between transactions for the simulation to work correctly. The documentation should clarify that:execute_bundle)Consider revising to: "Each transaction is simulated on the in-memory state produced by the previous transaction in the bundle. State changes are applied in-memory for subsequent transactions, but the final state is not persisted."