@@ -7,15 +7,17 @@ use crate::{
7
7
provider:: RootHasher ,
8
8
roothash:: RootHashError ,
9
9
utils:: {
10
- a2r_withdrawal, default_cfg_env, elapsed_ms,
10
+ a2r_withdrawal,
11
+ constants:: BASE_TX_GAS ,
12
+ default_cfg_env, elapsed_ms,
11
13
receipts:: {
12
14
calculate_receipt_root_and_block_logs_bloom, calculate_transactions_root, BloomCache ,
13
15
TransactionRootCache ,
14
16
} ,
15
17
timestamp_as_u64, Signer ,
16
18
} ,
17
19
} ;
18
- use alloy_consensus:: { Header , EMPTY_OMMER_ROOT_HASH } ;
20
+ use alloy_consensus:: { constants :: KECCAK_EMPTY , Header , EMPTY_OMMER_ROOT_HASH } ;
19
21
use alloy_eips:: {
20
22
eip1559:: { calculate_block_gas_limit, ETHEREUM_BLOCK_GAS_LIMIT_30M } ,
21
23
eip4844:: BlobTransactionSidecar ,
@@ -52,7 +54,7 @@ use revm::{
52
54
} ;
53
55
use serde:: Deserialize ;
54
56
use std:: {
55
- collections:: HashMap ,
57
+ collections:: { hash_map , HashMap } ,
56
58
hash:: Hash ,
57
59
str:: FromStr ,
58
60
sync:: Arc ,
@@ -427,6 +429,8 @@ pub struct PartialBlock<Tracer: SimulationTracer> {
427
429
pub coinbase_profit : U256 ,
428
430
/// Tx execution info belonging to successfully executed orders.
429
431
pub executed_tx_infos : Vec < TransactionExecutionInfo > ,
432
+ /// Combined refunds.
433
+ pub combined_refunds : HashMap < Address , U256 > ,
430
434
pub tracer : Tracer ,
431
435
}
432
436
@@ -450,6 +454,8 @@ pub enum InsertPayoutTxErr {
450
454
CriticalCommitError ( #[ from] CriticalCommitOrderError ) ,
451
455
#[ error( "Profit too low to insert payout tx" ) ]
452
456
ProfitTooLow ,
457
+ #[ error( "Combined refund tx reverted" ) ]
458
+ CombinedRefundTxReverted ,
453
459
#[ error( "Payout tx reverted" ) ]
454
460
PayoutTxReverted ,
455
461
#[ error( "Signer error: {0}" ) ]
@@ -542,6 +548,7 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
542
548
blob_gas_used : self . blob_gas_used ,
543
549
coinbase_profit : self . coinbase_profit ,
544
550
executed_tx_infos : self . executed_tx_infos ,
551
+ combined_refunds : self . combined_refunds ,
545
552
tracer,
546
553
}
547
554
}
@@ -580,6 +587,7 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
580
587
self . gas_reserved ,
581
588
self . blob_gas_used ,
582
589
self . discard_txs ,
590
+ & self . combined_refunds ,
583
591
) ?;
584
592
let ok_result = match exec_result {
585
593
Ok ( ok) => ok,
@@ -603,6 +611,18 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
603
611
self . blob_gas_used += ok_result. blob_gas_used ;
604
612
self . coinbase_profit += ok_result. coinbase_profit ;
605
613
self . executed_tx_infos . extend ( ok_result. tx_infos . clone ( ) ) ;
614
+
615
+ // Update combined refunds
616
+ if let Some ( ( address, refund_value) ) = ok_result. delayed_kickback {
617
+ let entry = self . combined_refunds . entry ( address) ;
618
+ if matches ! ( entry, hash_map:: Entry :: Vacant ( _) ) {
619
+ // This is the first refund for the recipient,
620
+ // so we need to reserve the gas for the refund tx.
621
+ self . gas_reserved += 21_000 ;
622
+ }
623
+ * entry. or_default ( ) += refund_value;
624
+ }
625
+
606
626
Ok ( Ok ( ExecutionResult {
607
627
coinbase_profit : ok_result. coinbase_profit ,
608
628
inplace_sim : inplace_sim_result,
@@ -628,7 +648,7 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
628
648
629
649
/// Inserts payout tx to ctx.attributes.suggested_fee_recipient (should be called at the end of the block)
630
650
/// Returns the paid value (block profit after subtracting the burned basefee of the payout tx)
631
- pub fn insert_proposer_payout_tx (
651
+ pub fn insert_refunds_and_proposer_payout_tx (
632
652
& mut self ,
633
653
gas_limit : u64 ,
634
654
value : U256 ,
@@ -641,13 +661,53 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
641
661
. as_ref ( )
642
662
. ok_or ( InsertPayoutTxErr :: NoSigner ) ?;
643
663
self . free_reserved_gas ( ) ;
644
- let nonce = state
664
+ let mut nonce = state
645
665
. nonce (
646
666
builder_signer. address ,
647
667
& ctx. shared_cached_reads ,
648
668
& mut local_ctx. cached_reads ,
649
669
)
650
670
. map_err ( CriticalCommitOrderError :: Reth ) ?;
671
+
672
+ let mut fork = PartialBlockFork :: new ( state, ctx, local_ctx) . with_tracer ( & mut self . tracer ) ;
673
+
674
+ for ( refund_recipient, refund_amount) in & self . combined_refunds {
675
+ let refund_recipient_code_hash = fork
676
+ . state
677
+ . code_hash (
678
+ * refund_recipient,
679
+ & ctx. shared_cached_reads ,
680
+ & mut fork. local_ctx . cached_reads ,
681
+ )
682
+ . map_err ( CriticalCommitOrderError :: Reth ) ?;
683
+ if refund_recipient_code_hash != KECCAK_EMPTY {
684
+ error ! ( %refund_recipient_code_hash, %refund_recipient, %refund_amount, "Refund recipient has code, skipping refund" ) ;
685
+ continue ;
686
+ }
687
+
688
+ let refund_tx = TransactionSignedEcRecoveredWithBlobs :: new_no_blobs ( create_payout_tx (
689
+ ctx. chain_spec . as_ref ( ) ,
690
+ ctx. evm_env . block_env . basefee ,
691
+ builder_signer,
692
+ nonce,
693
+ * refund_recipient,
694
+ BASE_TX_GAS ,
695
+ * refund_amount,
696
+ ) ?)
697
+ . unwrap ( ) ;
698
+ let refund_result =
699
+ fork. commit_tx ( & refund_tx, self . gas_used , 0 , self . blob_gas_used ) ??;
700
+ if !refund_result. tx_info . receipt . success {
701
+ return Err ( InsertPayoutTxErr :: CombinedRefundTxReverted ) ;
702
+ }
703
+
704
+ self . gas_used += refund_result. tx_info . gas_used ;
705
+ self . blob_gas_used += refund_result. blob_gas_used ;
706
+ self . executed_tx_infos . push ( refund_result. tx_info ) ;
707
+
708
+ nonce += 1 ;
709
+ }
710
+
651
711
let tx = create_payout_tx (
652
712
ctx. chain_spec . as_ref ( ) ,
653
713
ctx. evm_env . block_env . basefee ,
@@ -659,7 +719,6 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
659
719
) ?;
660
720
// payout tx has no blobs so it's safe to unwrap
661
721
let tx = TransactionSignedEcRecoveredWithBlobs :: new_no_blobs ( tx) . unwrap ( ) ;
662
- let mut fork = PartialBlockFork :: new ( state, ctx, local_ctx) . with_tracer ( & mut self . tracer ) ;
663
722
let exec_result = fork. commit_tx ( & tx, self . gas_used , 0 , self . blob_gas_used ) ?;
664
723
let ok_result = exec_result?;
665
724
if !ok_result. tx_info . receipt . success {
@@ -919,6 +978,7 @@ impl PartialBlock<()> {
919
978
blob_gas_used : 0 ,
920
979
coinbase_profit : U256 :: ZERO ,
921
980
executed_tx_infos : Vec :: new ( ) ,
981
+ combined_refunds : HashMap :: default ( ) ,
922
982
tracer : ( ) ,
923
983
}
924
984
}
@@ -1014,6 +1074,7 @@ mod test {
1014
1074
coinbase_profit: profit_2,
1015
1075
} ,
1016
1076
] ,
1077
+ delayed_kickback : None ,
1017
1078
original_order_ids : Default :: default ( ) ,
1018
1079
nonces_updated : Default :: default ( ) ,
1019
1080
paid_kickbacks : Default :: default ( ) ,
@@ -1056,6 +1117,7 @@ mod test {
1056
1117
gas_used: Default :: default ( ) ,
1057
1118
coinbase_profit: profit,
1058
1119
} ] ,
1120
+ delayed_kickback : None ,
1059
1121
original_order_ids : Default :: default ( ) ,
1060
1122
nonces_updated : Default :: default ( ) ,
1061
1123
paid_kickbacks : Default :: default ( ) ,
0 commit comments