@@ -3,7 +3,8 @@ use alloy_consensus::{Transaction, TxEip4844Variant, TxEnvelope};
3
3
use alloy_eips:: { eip2718:: Decodable2718 , BlockNumberOrTag } ;
4
4
use alloy_primitives:: { bytes:: Buf , keccak256, Address , TxKind , U256 } ;
5
5
use alloy_rpc_types_mev:: {
6
- EthCallBundle , EthCallBundleResponse , EthCallBundleTransactionResult , EthSendBundle ,
6
+ EthBundleHash , EthCallBundle , EthCallBundleResponse , EthCallBundleTransactionResult ,
7
+ EthSendBundle ,
7
8
} ;
8
9
use revm:: primitives:: { EVMError , ExecutionResult , MAX_BLOB_GAS_PER_BLOCK } ;
9
10
use thiserror:: Error ;
@@ -67,23 +68,24 @@ impl<Db: revm::Database> std::fmt::Debug for BundleError<Db> {
67
68
}
68
69
}
69
70
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.
71
73
#[ derive( Debug ) ]
72
- pub struct BundleSimulator < B , R > {
73
- /// The bundle to simulate .
74
+ pub struct BundleProcessor < B , R > {
75
+ /// The bundle to process .
74
76
pub bundle : B ,
75
- /// The response to the bundle simulation .
77
+ /// The response for the processed bundle .
76
78
pub response : R ,
77
79
}
78
80
79
- impl < B , R > BundleSimulator < B , R > {
81
+ impl < B , R > BundleProcessor < B , R > {
80
82
/// Create a new bundle simulator with the given bundle and response.
81
83
pub const fn new ( bundle : B , response : R ) -> Self {
82
84
Self { bundle, response }
83
85
}
84
86
}
85
87
86
- impl < Ext > BundleDriver < Ext > for BundleSimulator < EthCallBundle , EthCallBundleResponse > {
88
+ impl < Ext > BundleDriver < Ext > for BundleProcessor < EthCallBundle , EthCallBundleResponse > {
87
89
type Error < Db : revm:: Database > = BundleError < Db > ;
88
90
89
91
fn run_bundle < ' a , Db : revm:: Database + revm:: DatabaseCommit > (
@@ -319,6 +321,108 @@ impl<Ext> BundleDriver<Ext> for BundleSimulator<EthCallBundle, EthCallBundleResp
319
321
}
320
322
}
321
323
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
+
322
426
/// A block filler for the bundle, used to fill in the block data specified for the bundle.
323
427
#[ derive( Clone , Debug ) ]
324
428
struct BundleBlockFiller {
@@ -387,6 +491,18 @@ impl<Ext> BundleDriver<Ext> for EthCallBundle {
387
491
return Err ( trevm. errored ( BundleError :: BlockNumberMismatch ) ) ;
388
492
}
389
493
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
+
390
506
let bundle_filler = BundleBlockFiller :: from ( self . clone ( ) ) ;
391
507
392
508
let run_result = trevm. try_with_block ( & bundle_filler, |trevm| {
@@ -402,6 +518,17 @@ impl<Ext> BundleDriver<Ext> for EthCallBundle {
402
518
Err ( e) => return Err ( trevm. errored ( BundleError :: TransactionDecodingError ( e) ) ) ,
403
519
} ;
404
520
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
+
405
532
for tx in txs. iter ( ) {
406
533
let run_result = trevm. run_tx ( tx) ;
407
534
@@ -411,14 +538,9 @@ impl<Ext> BundleDriver<Ext> for EthCallBundle {
411
538
return Err ( e. map_err ( |e| BundleError :: EVMError { inner : e } ) ) ;
412
539
}
413
540
// 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
+ }
422
544
}
423
545
}
424
546
@@ -440,32 +562,40 @@ impl<Ext> BundleDriver<Ext> for EthCallBundle {
440
562
}
441
563
}
442
564
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.
443
568
impl < Ext > BundleDriver < Ext > for EthSendBundle {
444
569
type Error < Db : revm:: Database > = BundleError < Db > ;
445
570
446
571
fn run_bundle < ' a , Db : revm:: Database + revm:: DatabaseCommit > (
447
572
& mut self ,
448
573
trevm : crate :: EvmNeedsTx < ' a , Ext , Db > ,
449
574
) -> 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
451
576
if trevm. inner ( ) . block ( ) . number . to :: < u64 > ( ) != self . block_number {
452
577
return Err ( trevm. errored ( BundleError :: BlockNumberMismatch ) ) ;
453
578
}
454
579
455
- // 2. Check for start timestamp range validity
580
+ // Check for start timestamp range validity
456
581
if let Some ( min_timestamp) = self . min_timestamp {
457
582
if trevm. inner ( ) . block ( ) . timestamp . to :: < u64 > ( ) < min_timestamp {
458
583
return Err ( trevm. errored ( BundleError :: TimestampOutOfRange ) ) ;
459
584
}
460
585
}
461
586
462
- // 3. Check for end timestamp range validity
587
+ // Check for end timestamp range validity
463
588
if let Some ( max_timestamp) = self . max_timestamp {
464
589
if trevm. inner ( ) . block ( ) . timestamp . to :: < u64 > ( ) > max_timestamp {
465
590
return Err ( trevm. errored ( BundleError :: TimestampOutOfRange ) ) ;
466
591
}
467
592
}
468
593
594
+ // Check if the bundle has any transactions
595
+ if self . txs . is_empty ( ) {
596
+ return Err ( trevm. errored ( BundleError :: BundleEmpty ) ) ;
597
+ }
598
+
469
599
let txs = self
470
600
. txs
471
601
. iter ( )
@@ -476,6 +606,18 @@ impl<Ext> BundleDriver<Ext> for EthSendBundle {
476
606
Err ( e) => return Err ( trevm. errored ( BundleError :: TransactionDecodingError ( e) ) ) ,
477
607
} ;
478
608
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
479
621
let mut t = trevm;
480
622
481
623
for tx in txs. iter ( ) {
@@ -485,7 +627,8 @@ impl<Ext> BundleDriver<Ext> for EthSendBundle {
485
627
Err ( e) => return Err ( e. map_err ( |e| BundleError :: EVMError { inner : e } ) ) ,
486
628
} ;
487
629
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
489
632
let trevm = match run_result. result ( ) {
490
633
ExecutionResult :: Success { .. } => run_result. accept_state ( ) ,
491
634
ExecutionResult :: Revert { .. } | ExecutionResult :: Halt { .. } => {
0 commit comments