@@ -168,6 +168,8 @@ pub(crate) struct EthTransactionValidatorInner<Client, T> {
168
168
eip7702 : bool ,
169
169
/// The current max gas limit
170
170
block_gas_limit : AtomicU64 ,
171
+ /// The current tx fee cap limit in wei locally submitted into the pool.
172
+ tx_fee_cap : Option < u128 > ,
171
173
/// Minimum priority fee to enforce for acceptance into the pool.
172
174
minimum_priority_fee : Option < u128 > ,
173
175
/// Stores the setup and parameters needed for validating KZG proofs.
@@ -297,9 +299,35 @@ where
297
299
)
298
300
}
299
301
302
+ // determine whether the transaction should be treated as local
303
+ let is_local = self . local_transactions_config . is_local ( origin, transaction. sender_ref ( ) ) ;
304
+
305
+ // Ensure max possible transaction fee doesn't exceed configured transaction fee cap.
306
+ // Only for transactions locally submitted for acceptance into the pool.
307
+ if is_local {
308
+ match self . tx_fee_cap {
309
+ Some ( 0 ) | None => { } // Skip if cap is 0 or None
310
+ Some ( tx_fee_cap_wei) => {
311
+ // max possible tx fee is (gas_price * gas_limit)
312
+ // (if EIP1559) max possible tx fee is (max_fee_per_gas * gas_limit)
313
+ let gas_price = transaction. max_fee_per_gas ( ) ;
314
+ let max_tx_fee_wei = gas_price. saturating_mul ( transaction. gas_limit ( ) as u128 ) ;
315
+ if max_tx_fee_wei > tx_fee_cap_wei {
316
+ return TransactionValidationOutcome :: Invalid (
317
+ transaction,
318
+ InvalidPoolTransactionError :: ExceedsFeeCap {
319
+ max_tx_fee_wei,
320
+ tx_fee_cap_wei,
321
+ } ,
322
+ ) ;
323
+ }
324
+ }
325
+ }
326
+ }
327
+
300
328
// Drop non-local transactions with a fee lower than the configured fee for acceptance into
301
329
// the pool.
302
- if !self . local_transactions_config . is_local ( origin , transaction . sender_ref ( ) ) &&
330
+ if !is_local &&
303
331
transaction. is_dynamic_fee ( ) &&
304
332
transaction. max_priority_fee_per_gas ( ) < self . minimum_priority_fee
305
333
{
@@ -590,6 +618,8 @@ pub struct EthTransactionValidatorBuilder<Client> {
590
618
eip7702 : bool ,
591
619
/// The current max gas limit
592
620
block_gas_limit : AtomicU64 ,
621
+ /// The current tx fee cap limit in wei locally submitted into the pool.
622
+ tx_fee_cap : Option < u128 > ,
593
623
/// Minimum priority fee to enforce for acceptance into the pool.
594
624
minimum_priority_fee : Option < u128 > ,
595
625
/// Determines how many additional tasks to spawn
@@ -623,7 +653,7 @@ impl<Client> EthTransactionValidatorBuilder<Client> {
623
653
kzg_settings : EnvKzgSettings :: Default ,
624
654
local_transactions_config : Default :: default ( ) ,
625
655
max_tx_input_bytes : DEFAULT_MAX_TX_INPUT_BYTES ,
626
-
656
+ tx_fee_cap : Some ( 1e18 as u128 ) ,
627
657
// by default all transaction types are allowed
628
658
eip2718 : true ,
629
659
eip1559 : true ,
@@ -770,6 +800,14 @@ impl<Client> EthTransactionValidatorBuilder<Client> {
770
800
self
771
801
}
772
802
803
+ /// Sets the block gas limit
804
+ ///
805
+ /// Transactions with a gas limit greater than this will be rejected.
806
+ pub fn set_tx_fee_cap ( mut self , tx_fee_cap : u128 ) -> Self {
807
+ self . tx_fee_cap = Some ( tx_fee_cap) ;
808
+ self
809
+ }
810
+
773
811
/// Builds a the [`EthTransactionValidator`] without spawning validator tasks.
774
812
pub fn build < Tx , S > ( self , blob_store : S ) -> EthTransactionValidator < Client , Tx >
775
813
where
@@ -785,6 +823,7 @@ impl<Client> EthTransactionValidatorBuilder<Client> {
785
823
eip4844,
786
824
eip7702,
787
825
block_gas_limit,
826
+ tx_fee_cap,
788
827
minimum_priority_fee,
789
828
kzg_settings,
790
829
local_transactions_config,
@@ -813,6 +852,7 @@ impl<Client> EthTransactionValidatorBuilder<Client> {
813
852
eip4844,
814
853
eip7702,
815
854
block_gas_limit,
855
+ tx_fee_cap,
816
856
minimum_priority_fee,
817
857
blob_store : Box :: new ( blob_store) ,
818
858
kzg_settings,
@@ -1035,4 +1075,77 @@ mod tests {
1035
1075
let tx = pool. get ( transaction. hash ( ) ) ;
1036
1076
assert ! ( tx. is_none( ) ) ;
1037
1077
}
1078
+
1079
+ #[ tokio:: test]
1080
+ async fn invalid_on_fee_cap_exceeded ( ) {
1081
+ let transaction = get_transaction ( ) ;
1082
+ let provider = MockEthProvider :: default ( ) ;
1083
+ provider. add_account (
1084
+ transaction. sender ( ) ,
1085
+ ExtendedAccount :: new ( transaction. nonce ( ) , U256 :: MAX ) ,
1086
+ ) ;
1087
+
1088
+ let blob_store = InMemoryBlobStore :: default ( ) ;
1089
+ let validator = EthTransactionValidatorBuilder :: new ( provider)
1090
+ . set_tx_fee_cap ( 100 ) // 100 wei cap
1091
+ . build ( blob_store. clone ( ) ) ;
1092
+
1093
+ let outcome = validator. validate_one ( TransactionOrigin :: Local , transaction. clone ( ) ) ;
1094
+ assert ! ( outcome. is_invalid( ) ) ;
1095
+
1096
+ if let TransactionValidationOutcome :: Invalid ( _, err) = outcome {
1097
+ assert ! ( matches!(
1098
+ err,
1099
+ InvalidPoolTransactionError :: ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1100
+ if ( max_tx_fee_wei > tx_fee_cap_wei)
1101
+ ) ) ;
1102
+ }
1103
+
1104
+ let pool =
1105
+ Pool :: new ( validator, CoinbaseTipOrdering :: default ( ) , blob_store, Default :: default ( ) ) ;
1106
+ let res = pool. add_transaction ( TransactionOrigin :: Local , transaction. clone ( ) ) . await ;
1107
+ assert ! ( res. is_err( ) ) ;
1108
+ assert ! ( matches!(
1109
+ res. unwrap_err( ) . kind,
1110
+ PoolErrorKind :: InvalidTransaction ( InvalidPoolTransactionError :: ExceedsFeeCap { .. } )
1111
+ ) ) ;
1112
+ let tx = pool. get ( transaction. hash ( ) ) ;
1113
+ assert ! ( tx. is_none( ) ) ;
1114
+ }
1115
+
1116
+ #[ tokio:: test]
1117
+ async fn valid_on_zero_fee_cap ( ) {
1118
+ let transaction = get_transaction ( ) ;
1119
+ let provider = MockEthProvider :: default ( ) ;
1120
+ provider. add_account (
1121
+ transaction. sender ( ) ,
1122
+ ExtendedAccount :: new ( transaction. nonce ( ) , U256 :: MAX ) ,
1123
+ ) ;
1124
+
1125
+ let blob_store = InMemoryBlobStore :: default ( ) ;
1126
+ let validator = EthTransactionValidatorBuilder :: new ( provider)
1127
+ . set_tx_fee_cap ( 0 ) // no cap
1128
+ . build ( blob_store) ;
1129
+
1130
+ let outcome = validator. validate_one ( TransactionOrigin :: Local , transaction) ;
1131
+ assert ! ( outcome. is_valid( ) ) ;
1132
+ }
1133
+
1134
+ #[ tokio:: test]
1135
+ async fn valid_on_normal_fee_cap ( ) {
1136
+ let transaction = get_transaction ( ) ;
1137
+ let provider = MockEthProvider :: default ( ) ;
1138
+ provider. add_account (
1139
+ transaction. sender ( ) ,
1140
+ ExtendedAccount :: new ( transaction. nonce ( ) , U256 :: MAX ) ,
1141
+ ) ;
1142
+
1143
+ let blob_store = InMemoryBlobStore :: default ( ) ;
1144
+ let validator = EthTransactionValidatorBuilder :: new ( provider)
1145
+ . set_tx_fee_cap ( 2e18 as u128 ) // 2 ETH cap
1146
+ . build ( blob_store) ;
1147
+
1148
+ let outcome = validator. validate_one ( TransactionOrigin :: Local , transaction) ;
1149
+ assert ! ( outcome. is_valid( ) ) ;
1150
+ }
1038
1151
}
0 commit comments