@@ -21,15 +21,15 @@ use reth_node_builder::{
2121 BuilderContext ,
2222} ;
2323use reth_primitives_traits:: NodePrimitives ;
24- use reth_storage_api:: { AccountInfoReader , StateProviderFactory } ;
24+ use reth_storage_api:: { AccountInfoReader , BlockNumReader , StateProviderFactory } ;
2525use reth_transaction_pool:: {
2626 blobstore:: DiskFileBlobStore ,
2727 error:: { InvalidPoolTransactionError , PoolTransactionError } ,
2828 CoinbaseTipOrdering , EthBlobTransactionSidecar , EthPoolTransaction , EthPooledTransaction ,
2929 EthTransactionValidator , PoolTransaction , TransactionOrigin , TransactionValidationOutcome ,
3030 TransactionValidationTaskExecutor , TransactionValidator ,
3131} ;
32- use tracing:: { debug, info} ;
32+ use tracing:: { debug, info, warn } ;
3333
3434/// Pool transaction wrapper for `EvTxEnvelope`.
3535#[ derive( Debug , Clone ) ]
@@ -286,13 +286,19 @@ pub enum EvTxPoolError {
286286 /// Error while querying account info from the state provider.
287287 #[ error( "state provider error: {0}" ) ]
288288 StateProvider ( String ) ,
289+ /// Top-level contract deployment not allowed for caller.
290+ #[ error( "contract deployment not allowed" ) ]
291+ DeployNotAllowed ,
289292}
290293
291294impl PoolTransactionError for EvTxPoolError {
292295 fn is_bad_transaction ( & self ) -> bool {
293296 matches ! (
294297 self ,
295- Self :: EmptyCalls | Self :: InvalidCreatePosition | Self :: InvalidSponsorSignature
298+ Self :: EmptyCalls
299+ | Self :: InvalidCreatePosition
300+ | Self :: InvalidSponsorSignature
301+ | Self :: DeployNotAllowed
296302 )
297303 }
298304
@@ -305,13 +311,24 @@ impl PoolTransactionError for EvTxPoolError {
305311#[ derive( Debug , Clone ) ]
306312pub struct EvTransactionValidator < Client > {
307313 inner : Arc < EthTransactionValidator < Client , EvPooledTransaction > > ,
314+ deploy_allowlist : Option < ev_revm:: deploy:: DeployAllowlistSettings > ,
308315}
309316
310- impl < Client > EvTransactionValidator < Client > {
317+ impl < Client > EvTransactionValidator < Client >
318+ where
319+ Client : BlockNumReader ,
320+ {
311321 /// Wraps the provided Ethereum validator with EV-specific validation logic.
312- pub fn new ( inner : EthTransactionValidator < Client , EvPooledTransaction > ) -> Self {
322+ pub fn new (
323+ inner : EthTransactionValidator < Client , EvPooledTransaction > ,
324+ deploy_allowlist : Option < ev_revm:: deploy:: DeployAllowlistSettings > ,
325+ ) -> Self
326+ where
327+ Client : BlockNumReader ,
328+ {
313329 Self {
314330 inner : Arc :: new ( inner) ,
331+ deploy_allowlist,
315332 }
316333 }
317334
@@ -383,6 +400,31 @@ impl<Client> EvTransactionValidator<Client> {
383400 where
384401 Client : StateProviderFactory ,
385402 {
403+ // Unified deploy allowlist check (covers both Ethereum and EvNode txs).
404+ if let Some ( settings) = & self . deploy_allowlist {
405+ let is_top_level_create = match pooled. transaction ( ) . inner ( ) {
406+ EvTxEnvelope :: Ethereum ( tx) => alloy_consensus:: Transaction :: is_create ( tx) ,
407+ EvTxEnvelope :: EvNode ( ref signed) => {
408+ let tx = signed. tx ( ) ;
409+ tx. calls . first ( ) . map ( |c| c. to . is_create ( ) ) . unwrap_or ( false )
410+ }
411+ } ;
412+ let caller = pooled. transaction ( ) . signer ( ) ;
413+ let block_number = self . inner . client ( ) . best_block_number ( ) . map_err ( |err| {
414+ InvalidPoolTransactionError :: other ( EvTxPoolError :: StateProvider ( err. to_string ( ) ) )
415+ } ) ?;
416+ if let Err ( _e) = ev_revm:: deploy:: check_deploy_allowed (
417+ Some ( settings) ,
418+ caller,
419+ is_top_level_create,
420+ block_number,
421+ ) {
422+ return Err ( InvalidPoolTransactionError :: other (
423+ EvTxPoolError :: DeployNotAllowed ,
424+ ) ) ;
425+ }
426+ }
427+
386428 let consensus = pooled. transaction ( ) . inner ( ) ;
387429 let EvTxEnvelope :: EvNode ( tx) = consensus else {
388430 if sender_balance < * pooled. cost ( ) {
@@ -422,7 +464,7 @@ impl<Client> EvTransactionValidator<Client> {
422464
423465impl < Client > TransactionValidator for EvTransactionValidator < Client >
424466where
425- Client : ChainSpecProvider < ChainSpec : EthereumHardforks > + StateProviderFactory ,
467+ Client : ChainSpecProvider < ChainSpec : EthereumHardforks > + StateProviderFactory + BlockNumReader ,
426468{
427469 type Transaction = EvPooledTransaction ;
428470
@@ -470,7 +512,7 @@ pub struct EvolvePoolBuilder;
470512impl < Types , Node > PoolBuilder < Node > for EvolvePoolBuilder
471513where
472514 Types : NodeTypes <
473- ChainSpec : EthereumHardforks ,
515+ ChainSpec = reth_chainspec :: ChainSpec ,
474516 Primitives : NodePrimitives < SignedTx = TransactionSigned > ,
475517 > ,
476518 Node : FullNodeTypes < Types = Types > ,
@@ -521,7 +563,26 @@ where
521563 ctx. task_executor ( ) . clone ( ) ,
522564 blob_store. clone ( ) ,
523565 )
524- . map ( EvTransactionValidator :: new) ;
566+ . map ( |inner| {
567+ // Wire deploy-allowlist from chainspec extras into the pool validator.
568+ let evolve_config = crate :: config:: EvolvePayloadBuilderConfig :: from_chain_spec (
569+ ctx. chain_spec ( ) . as_ref ( ) ,
570+ )
571+ . unwrap_or_else ( |err| {
572+ warn ! (
573+ target: "reth::cli" ,
574+ "Failed to parse evolve config from chainspec: {err}"
575+ ) ;
576+ Default :: default ( )
577+ } ) ;
578+ let deploy_allowlist =
579+ evolve_config
580+ . deploy_allowlist_settings ( )
581+ . map ( |( allowlist, activation) | {
582+ ev_revm:: deploy:: DeployAllowlistSettings :: new ( allowlist, activation)
583+ } ) ;
584+ EvTransactionValidator :: new ( inner, deploy_allowlist)
585+ } ) ;
525586
526587 if validator. validator ( ) . inner . eip4844 ( ) {
527588 let kzg_settings = validator. validator ( ) . inner . kzg_settings ( ) . clone ( ) ;
@@ -576,14 +637,38 @@ mod tests {
576637 Signed :: new_unhashed ( tx, sample_signature ( ) )
577638 }
578639
640+ /// Creates a non-sponsored `EvNode` transaction with CREATE as the first call.
641+ fn create_non_sponsored_evnode_create_tx (
642+ gas_limit : u64 ,
643+ max_fee_per_gas : u128 ,
644+ ) -> EvNodeSignedTx {
645+ let tx = EvNodeTransaction {
646+ chain_id : 1 ,
647+ nonce : 0 ,
648+ max_priority_fee_per_gas : 1 ,
649+ max_fee_per_gas,
650+ gas_limit,
651+ calls : vec ! [ Call {
652+ to: TxKind :: Create ,
653+ value: U256 :: ZERO ,
654+ input: Bytes :: from_static( & [ 0x60 , 0x00 , 0x60 , 0x00 , 0xf3 ] ) , // minimal initcode
655+ } ] ,
656+ access_list : AccessList :: default ( ) ,
657+ fee_payer_signature : None ,
658+ } ;
659+ Signed :: new_unhashed ( tx, sample_signature ( ) )
660+ }
661+
579662 fn create_pooled_tx ( signed_tx : EvNodeSignedTx , signer : Address ) -> EvPooledTransaction {
580663 let envelope = EvTxEnvelope :: EvNode ( signed_tx) ;
581664 let recovered = alloy_consensus:: transaction:: Recovered :: new_unchecked ( envelope, signer) ;
582665 let encoded_length = 200 ; // Approximate length for test
583666 EvPooledTransaction :: new ( recovered, encoded_length)
584667 }
585668
586- fn create_test_validator ( ) -> EvTransactionValidator < MockEthProvider > {
669+ fn create_test_validator (
670+ deploy_allowlist : Option < ev_revm:: deploy:: DeployAllowlistSettings > ,
671+ ) -> EvTransactionValidator < MockEthProvider > {
587672 use reth_transaction_pool:: {
588673 blobstore:: InMemoryBlobStore , validate:: EthTransactionValidatorBuilder ,
589674 } ;
@@ -594,7 +679,7 @@ mod tests {
594679 . no_shanghai ( )
595680 . no_cancun ( )
596681 . build ( blob_store) ;
597- EvTransactionValidator :: new ( inner)
682+ EvTransactionValidator :: new ( inner, deploy_allowlist )
598683 }
599684
600685 /// Tests that non-sponsored `EvNode` transactions with insufficient sender balance
@@ -604,7 +689,7 @@ mod tests {
604689 /// sender balance for non-sponsored `EvNode` transactions.
605690 #[ test]
606691 fn non_sponsored_evnode_rejects_insufficient_balance ( ) {
607- let validator = create_test_validator ( ) ;
692+ let validator = create_test_validator ( None ) ;
608693
609694 // Create a non-sponsored EvNode transaction
610695 let gas_limit = 21_000u64 ;
@@ -638,7 +723,7 @@ mod tests {
638723 /// Tests that non-sponsored `EvNode` transactions with sufficient balance are accepted.
639724 #[ test]
640725 fn non_sponsored_evnode_accepts_sufficient_balance ( ) {
641- let validator = create_test_validator ( ) ;
726+ let validator = create_test_validator ( None ) ;
642727
643728 let gas_limit = 21_000u64 ;
644729 let max_fee_per_gas = 1_000_000_000u128 ;
@@ -661,4 +746,29 @@ mod tests {
661746 result
662747 ) ;
663748 }
749+
750+ /// Tests pool-level deploy allowlist rejection for `EvNode` CREATE when caller not allowlisted.
751+ #[ test]
752+ fn evnode_create_rejected_when_not_allowlisted ( ) {
753+ // Configure deploy allowlist with a different address than the signer
754+ let allowed = Address :: from ( [ 0x11u8 ; 20 ] ) ;
755+ let settings = ev_revm:: deploy:: DeployAllowlistSettings :: new ( vec ! [ allowed] , 0 ) ;
756+ let validator = create_test_validator ( Some ( settings) ) ;
757+
758+ let gas_limit = 200_000u64 ;
759+ let max_fee_per_gas = 1_000_000_000u128 ;
760+ let signed_tx = create_non_sponsored_evnode_create_tx ( gas_limit, max_fee_per_gas) ;
761+
762+ let signer = Address :: from ( [ 0x22u8 ; 20 ] ) ; // not allowlisted
763+ let pooled = create_pooled_tx ( signed_tx, signer) ;
764+
765+ let sender_balance = * pooled. cost ( ) + U256 :: from ( 1 ) ;
766+ let mut state: Option < Box < dyn AccountInfoReader > > = None ;
767+
768+ let result = validator. validate_evnode ( & pooled, sender_balance, & mut state) ;
769+ assert ! ( result. is_err( ) ) ;
770+ if let Err ( err) = result {
771+ assert ! ( matches!( err, InvalidPoolTransactionError :: Other ( _) ) ) ;
772+ }
773+ }
664774}
0 commit comments