Skip to content

Commit 02bbba0

Browse files
committed
feat: ethsendbundle/ethbundle hash impl, bundlesimulator -> blockprocessor
1 parent e53b298 commit 02bbba0

File tree

3 files changed

+164
-21
lines changed

3 files changed

+164
-21
lines changed

src/driver/alloy.rs

Lines changed: 162 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use alloy_consensus::{Transaction, TxEip4844Variant, TxEnvelope};
33
use alloy_eips::{eip2718::Decodable2718, BlockNumberOrTag};
44
use alloy_primitives::{bytes::Buf, keccak256, Address, TxKind, U256};
55
use alloy_rpc_types_mev::{
6-
EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult, EthSendBundle,
6+
EthBundleHash, EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult,
7+
EthSendBundle,
78
};
89
use revm::primitives::{EVMError, ExecutionResult, MAX_BLOB_GAS_PER_BLOCK};
910
use thiserror::Error;
@@ -67,23 +68,24 @@ impl<Db: revm::Database> std::fmt::Debug for BundleError<Db> {
6768
}
6869
}
6970

70-
/// A bundle simulator which can be used to drive a bundle with a [BundleDriver] and accumulate the results of the bundle.
71+
/// A bundle processor which can be used to drive a bundle with a [BundleDriver], accumulate the results of the bundle and dispatch
72+
/// a response.
7173
#[derive(Debug)]
72-
pub struct BundleSimulator<B, R> {
73-
/// The bundle to simulate.
74+
pub struct BundleProcessor<B, R> {
75+
/// The bundle to process.
7476
pub bundle: B,
75-
/// The response to the bundle simulation.
77+
/// The response for the processed bundle.
7678
pub response: R,
7779
}
7880

79-
impl<B, R> BundleSimulator<B, R> {
81+
impl<B, R> BundleProcessor<B, R> {
8082
/// Create a new bundle simulator with the given bundle and response.
8183
pub const fn new(bundle: B, response: R) -> Self {
8284
Self { bundle, response }
8385
}
8486
}
8587

86-
impl<Ext> BundleDriver<Ext> for BundleSimulator<EthCallBundle, EthCallBundleResponse> {
88+
impl<Ext> BundleDriver<Ext> for BundleProcessor<EthCallBundle, EthCallBundleResponse> {
8789
type Error<Db: revm::Database> = BundleError<Db>;
8890

8991
fn run_bundle<'a, Db: revm::Database + revm::DatabaseCommit>(
@@ -319,6 +321,108 @@ impl<Ext> BundleDriver<Ext> for BundleSimulator<EthCallBundle, EthCallBundleResp
319321
}
320322
}
321323

324+
impl<Ext> BundleDriver<Ext> for BundleProcessor<EthSendBundle, EthBundleHash> {
325+
type Error<Db: revm::Database> = BundleError<Db>;
326+
327+
fn run_bundle<'a, Db: revm::Database + revm::DatabaseCommit>(
328+
&mut self,
329+
trevm: crate::EvmNeedsTx<'a, Ext, Db>,
330+
) -> DriveBundleResult<'a, Ext, Db, Self> {
331+
{
332+
// Check if the block we're in is valid for this bundle. Both must match
333+
if trevm.inner().block().number.to::<u64>() != self.bundle.block_number {
334+
return Err(trevm.errored(BundleError::BlockNumberMismatch));
335+
}
336+
337+
// Check for start timestamp range validity
338+
if let Some(min_timestamp) = self.bundle.min_timestamp {
339+
if trevm.inner().block().timestamp.to::<u64>() < min_timestamp {
340+
return Err(trevm.errored(BundleError::TimestampOutOfRange));
341+
}
342+
}
343+
344+
// Check for end timestamp range validity
345+
if let Some(max_timestamp) = self.bundle.max_timestamp {
346+
if trevm.inner().block().timestamp.to::<u64>() > max_timestamp {
347+
return Err(trevm.errored(BundleError::TimestampOutOfRange));
348+
}
349+
}
350+
351+
// Check if the bundle has any transactions
352+
if self.bundle.txs.is_empty() {
353+
return Err(trevm.errored(BundleError::BundleEmpty));
354+
}
355+
356+
let txs = self
357+
.bundle
358+
.txs
359+
.iter()
360+
.map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
361+
.collect::<Result<Vec<_>, _>>();
362+
let txs = match txs {
363+
Ok(txs) => txs,
364+
Err(e) => return Err(trevm.errored(BundleError::TransactionDecodingError(e))),
365+
};
366+
367+
// Check that the bundle does not exceed the maximum gas limit for blob transactions
368+
if txs
369+
.iter()
370+
.filter_map(|tx| tx.as_eip4844())
371+
.map(|tx| tx.tx().tx().blob_gas())
372+
.sum::<u64>()
373+
> MAX_BLOB_GAS_PER_BLOCK
374+
{
375+
return Err(trevm.errored(BundleError::Eip4844BlobGasExceeded));
376+
}
377+
378+
// Store the current evm state in this mutable variable, so we can continually use the freshest state for each simulation
379+
let mut t = trevm;
380+
381+
let mut hash_bytes = Vec::with_capacity(32 * txs.len());
382+
383+
for tx in txs.iter() {
384+
hash_bytes.extend_from_slice(tx.tx_hash().as_slice());
385+
// Run the transaction
386+
let run_result = match t.run_tx(tx) {
387+
Ok(res) => res,
388+
Err(e) => return Err(e.map_err(|e| BundleError::EVMError { inner: e })),
389+
};
390+
391+
// Accept the state if the transaction was successful and the bundle did not revert or halt AND
392+
// the tx that reverted is NOT in the set of transactions allowed to revert
393+
let trevm = match run_result.result() {
394+
ExecutionResult::Success { .. } => run_result.accept_state(),
395+
ExecutionResult::Revert { .. } | ExecutionResult::Halt { .. } => {
396+
// If the transaction reverted but it is contained in the set of transactions allowed to revert,
397+
// then we _accept_ the state and move on.
398+
// See https://github.com/flashbots/rbuilder/blob/52fea312e5d8be1f1405c52d1fd207ecee2d14b1/crates/rbuilder/src/building/order_commit.rs#L546-L558
399+
if self.bundle.reverting_tx_hashes.contains(tx.tx_hash()) {
400+
run_result.accept_state()
401+
} else {
402+
return Err(run_result.errored(BundleError::BundleReverted));
403+
}
404+
}
405+
};
406+
407+
// Make sure to update the trevm instance we're using to simulate with the latest one
408+
t = trevm;
409+
}
410+
411+
// Populate the response, which in this case just means setting the bundle hash
412+
self.response.bundle_hash = keccak256(hash_bytes);
413+
414+
Ok(t)
415+
}
416+
}
417+
418+
fn post_bundle<Db: revm::Database + revm::DatabaseCommit>(
419+
&mut self,
420+
_trevm: &crate::EvmNeedsTx<'_, Ext, Db>,
421+
) -> Result<(), Self::Error<Db>> {
422+
Ok(())
423+
}
424+
}
425+
322426
/// A block filler for the bundle, used to fill in the block data specified for the bundle.
323427
#[derive(Clone, Debug)]
324428
struct BundleBlockFiller {
@@ -387,6 +491,18 @@ impl<Ext> BundleDriver<Ext> for EthCallBundle {
387491
return Err(trevm.errored(BundleError::BlockNumberMismatch));
388492
}
389493

494+
// Check if the bundle has any transactions
495+
if self.txs.is_empty() {
496+
return Err(trevm.errored(BundleError::BundleEmpty));
497+
}
498+
499+
// Check if the state block number is valid (not 0, and not a tag)
500+
if !self.state_block_number.is_number()
501+
|| self.state_block_number.as_number().unwrap_or(0) == 0
502+
{
503+
return Err(trevm.errored(BundleError::BlockNumberMismatch));
504+
}
505+
390506
let bundle_filler = BundleBlockFiller::from(self.clone());
391507

392508
let run_result = trevm.try_with_block(&bundle_filler, |trevm| {
@@ -402,6 +518,17 @@ impl<Ext> BundleDriver<Ext> for EthCallBundle {
402518
Err(e) => return Err(trevm.errored(BundleError::TransactionDecodingError(e))),
403519
};
404520

521+
// Check that the bundle does not exceed the maximum gas limit for blob transactions
522+
if txs
523+
.iter()
524+
.filter_map(|tx| tx.as_eip4844())
525+
.map(|tx| tx.tx().tx().blob_gas())
526+
.sum::<u64>()
527+
> MAX_BLOB_GAS_PER_BLOCK
528+
{
529+
return Err(trevm.errored(BundleError::Eip4844BlobGasExceeded));
530+
}
531+
405532
for tx in txs.iter() {
406533
let run_result = trevm.run_tx(tx);
407534

@@ -411,14 +538,9 @@ impl<Ext> BundleDriver<Ext> for EthCallBundle {
411538
return Err(e.map_err(|e| BundleError::EVMError { inner: e }));
412539
}
413540
// Accept the state, and move on
414-
Ok(res) => match res.result() {
415-
ExecutionResult::Revert { .. } | ExecutionResult::Halt { .. } => {
416-
return Err(res.errored(BundleError::BundleReverted));
417-
}
418-
ExecutionResult::Success { .. } => {
419-
trevm = res.accept_state();
420-
}
421-
},
541+
Ok(res) => {
542+
trevm = res.accept_state();
543+
}
422544
}
423545
}
424546

@@ -440,32 +562,40 @@ impl<Ext> BundleDriver<Ext> for EthCallBundle {
440562
}
441563
}
442564

565+
/// An implementation of [BundleDriver] for [EthSendBundle].
566+
/// This allows us to drive a bundle of transactions and accumulate the resulting state in the EVM.
567+
/// Allows to simply take an [EthSendBundle] and get the resulting EVM state.
443568
impl<Ext> BundleDriver<Ext> for EthSendBundle {
444569
type Error<Db: revm::Database> = BundleError<Db>;
445570

446571
fn run_bundle<'a, Db: revm::Database + revm::DatabaseCommit>(
447572
&mut self,
448573
trevm: crate::EvmNeedsTx<'a, Ext, Db>,
449574
) -> DriveBundleResult<'a, Ext, Db, Self> {
450-
// 1. Check if the block we're in is valid for this bundle. Both must match
575+
// Check if the block we're in is valid for this bundle. Both must match
451576
if trevm.inner().block().number.to::<u64>() != self.block_number {
452577
return Err(trevm.errored(BundleError::BlockNumberMismatch));
453578
}
454579

455-
// 2. Check for start timestamp range validity
580+
// Check for start timestamp range validity
456581
if let Some(min_timestamp) = self.min_timestamp {
457582
if trevm.inner().block().timestamp.to::<u64>() < min_timestamp {
458583
return Err(trevm.errored(BundleError::TimestampOutOfRange));
459584
}
460585
}
461586

462-
// 3. Check for end timestamp range validity
587+
// Check for end timestamp range validity
463588
if let Some(max_timestamp) = self.max_timestamp {
464589
if trevm.inner().block().timestamp.to::<u64>() > max_timestamp {
465590
return Err(trevm.errored(BundleError::TimestampOutOfRange));
466591
}
467592
}
468593

594+
// Check if the bundle has any transactions
595+
if self.txs.is_empty() {
596+
return Err(trevm.errored(BundleError::BundleEmpty));
597+
}
598+
469599
let txs = self
470600
.txs
471601
.iter()
@@ -476,6 +606,18 @@ impl<Ext> BundleDriver<Ext> for EthSendBundle {
476606
Err(e) => return Err(trevm.errored(BundleError::TransactionDecodingError(e))),
477607
};
478608

609+
// Check that the bundle does not exceed the maximum gas limit for blob transactions
610+
if txs
611+
.iter()
612+
.filter_map(|tx| tx.as_eip4844())
613+
.map(|tx| tx.tx().tx().blob_gas())
614+
.sum::<u64>()
615+
> MAX_BLOB_GAS_PER_BLOCK
616+
{
617+
return Err(trevm.errored(BundleError::Eip4844BlobGasExceeded));
618+
}
619+
620+
// Store the current evm state in this mutable variable, so we can continually use the freshest state for each simulation
479621
let mut t = trevm;
480622

481623
for tx in txs.iter() {
@@ -485,7 +627,8 @@ impl<Ext> BundleDriver<Ext> for EthSendBundle {
485627
Err(e) => return Err(e.map_err(|e| BundleError::EVMError { inner: e })),
486628
};
487629

488-
// Accept the state if the transaction was successful and the bundle did not revert or halt
630+
// Accept the state if the transaction was successful and the bundle did not revert or halt AND
631+
// the tx that reverted is NOT in the set of transactions allowed to revert
489632
let trevm = match run_result.result() {
490633
ExecutionResult::Success { .. } => run_result.accept_state(),
491634
ExecutionResult::Revert { .. } | ExecutionResult::Halt { .. } => {

src/driver/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mod alloy;
2-
pub use alloy::{BundleError, BundleSimulator};
2+
pub use alloy::{BundleError, BundleProcessor};
33

44
mod block;
55
pub use block::{BlockDriver, DriveBlockResult, RunTxResult};

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@
364364

365365
mod driver;
366366
pub use driver::{
367-
BlockDriver, BundleDriver, BundleError, BundleSimulator, ChainDriver, DriveBlockResult,
367+
BlockDriver, BundleDriver, BundleError, BundleProcessor, ChainDriver, DriveBlockResult,
368368
DriveBundleResult, DriveChainResult, RunTxResult,
369369
};
370370

0 commit comments

Comments
 (0)