From d077107f891674100f47339c20117a63275bae2d Mon Sep 17 00:00:00 2001 From: Sylvain Ribstein Date: Wed, 19 Mar 2025 12:00:11 +0100 Subject: [PATCH] evm/node: move data size validation in validate module --- etherlink/bin_node/lib_dev/observer.ml | 1 - etherlink/bin_node/lib_dev/proxy.ml | 1 - etherlink/bin_node/lib_dev/rpc.ml | 1 - etherlink/bin_node/lib_dev/sequencer.ml | 4 - etherlink/bin_node/lib_dev/services.ml | 11 ++- etherlink/bin_node/lib_dev/tx_pool.ml | 117 ++++++++++-------------- etherlink/bin_node/lib_dev/tx_pool.mli | 3 - etherlink/bin_node/lib_dev/validate.ml | 50 ++++++++-- etherlink/bin_node/lib_dev/validate.mli | 1 + 9 files changed, 100 insertions(+), 89 deletions(-) diff --git a/etherlink/bin_node/lib_dev/observer.ml b/etherlink/bin_node/lib_dev/observer.ml index ba279f97d23d..7833452eff75 100644 --- a/etherlink/bin_node/lib_dev/observer.ml +++ b/etherlink/bin_node/lib_dev/observer.ml @@ -182,7 +182,6 @@ let main ?network ?kernel_path ~data_dir ~(config : Configuration.t) ~no_sync tx_pool_addr_limit = Int64.to_int config.tx_pool_addr_limit; tx_pool_tx_per_addr_limit = Int64.to_int config.tx_pool_tx_per_addr_limit; - max_number_of_chunks = None; } in Metrics.init diff --git a/etherlink/bin_node/lib_dev/proxy.ml b/etherlink/bin_node/lib_dev/proxy.ml index 9f8b2670aa78..dbb0770b0c1a 100644 --- a/etherlink/bin_node/lib_dev/proxy.ml +++ b/etherlink/bin_node/lib_dev/proxy.ml @@ -86,7 +86,6 @@ let main tx_pool_addr_limit = Int64.to_int config.tx_pool_addr_limit; tx_pool_tx_per_addr_limit = Int64.to_int config.tx_pool_tx_per_addr_limit; - max_number_of_chunks = None; } in let () = diff --git a/etherlink/bin_node/lib_dev/rpc.ml b/etherlink/bin_node/lib_dev/rpc.ml index c0e100e4efbd..accd8a1f3df5 100644 --- a/etherlink/bin_node/lib_dev/rpc.ml +++ b/etherlink/bin_node/lib_dev/rpc.ml @@ -101,7 +101,6 @@ let main ~data_dir ~evm_node_endpoint ?evm_node_private_endpoint tx_pool_addr_limit = Int64.to_int config.tx_pool_addr_limit; tx_pool_tx_per_addr_limit = Int64.to_int config.tx_pool_tx_per_addr_limit; - max_number_of_chunks = None; } in diff --git a/etherlink/bin_node/lib_dev/sequencer.ml b/etherlink/bin_node/lib_dev/sequencer.ml index 7b010b74fced..1e2d62fa1c24 100644 --- a/etherlink/bin_node/lib_dev/sequencer.ml +++ b/etherlink/bin_node/lib_dev/sequencer.ml @@ -214,10 +214,6 @@ let main ~data_dir ?(genesis_timestamp = Misc.now ()) ~cctxt tx_pool_addr_limit = Int64.to_int configuration.tx_pool_addr_limit; tx_pool_tx_per_addr_limit = Int64.to_int configuration.tx_pool_tx_per_addr_limit; - max_number_of_chunks = - (match configuration.sequencer with - | Some {max_number_of_chunks; _} -> Some max_number_of_chunks - | None -> None); } in Metrics.init diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index 48d2338f2bf8..b3d2dcdd7024 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -713,10 +713,19 @@ let dispatch_request (rpc_server_family : Rpc_types.rpc_server_family) transactions" None else + let max_number_of_chunks = + Option.map + (fun seq -> seq.Configuration.max_number_of_chunks) + config.sequencer + in let f tx_raw = let txn = Ethereum_types.hex_to_bytes tx_raw in let* is_valid = - Validate.is_tx_valid (module Backend_rpc) ~mode:validation txn + Validate.is_tx_valid + ?max_number_of_chunks + (module Backend_rpc) + ~mode:validation + txn in match is_valid with | Error err -> diff --git a/etherlink/bin_node/lib_dev/tx_pool.ml b/etherlink/bin_node/lib_dev/tx_pool.ml index 75e6a11c0184..2abc41493674 100644 --- a/etherlink/bin_node/lib_dev/tx_pool.ml +++ b/etherlink/bin_node/lib_dev/tx_pool.ml @@ -240,7 +240,6 @@ type parameters = { tx_timeout_limit : int64; tx_pool_addr_limit : int; tx_pool_tx_per_addr_limit : int; - max_number_of_chunks : int option; } module Types = struct @@ -253,7 +252,6 @@ module Types = struct tx_timeout_limit : int64; tx_pool_addr_limit : int; tx_pool_tx_per_addr_limit : int; - max_number_of_chunks : int option; mutable locked : bool; } @@ -405,18 +403,6 @@ module Worker = Worker.MakeSingle (Name) (Request) (Types) type worker = Worker.infinite Worker.queue Worker.t -let tx_data_size_limit_reached ~max_number_of_chunks ~tx_data = - let maximum_chunks_per_l1_level = - Option.value - ~default:Sequencer_blueprint.maximum_chunks_per_l1_level - max_number_of_chunks - in - Bytes.length tx_data - > Sequencer_blueprint.maximum_usable_space_in_blueprint - (* Minus one so that the "rest" of the raw transaction can - be contained within one of the chunks. *) - (maximum_chunks_per_l1_level - 1) - let check_address_boundaries ~pool ~address ~tx_pool_addr_limit ~tx_pool_tx_per_addr_limit = let open Lwt_result_syntax in @@ -446,68 +432,59 @@ let insert_valid_transaction state tx_raw pool; tx_pool_addr_limit; tx_pool_tx_per_addr_limit; - max_number_of_chunks; _; } = state in - let tx_data = - transaction_object.input |> Ethereum_types.hex_to_bytes |> Bytes.of_string + let add_transaction ~must_replace = + (* Add the transaction to the pool *) + match Pool.add ~must_replace pool tx_raw transaction_object with + | Ok pool -> + let*! () = + Tx_pool_events.add_transaction + ~transaction:(Ethereum_types.hash_to_string transaction_object.hash) + in + state.pool <- pool ; + return (Ok transaction_object.hash) + | Error msg -> return (Error msg) in - if tx_data_size_limit_reached ~max_number_of_chunks ~tx_data then - let*! () = Tx_pool_events.tx_data_size_limit_reached () in - return @@ Error "Transaction data exceeded the allowed size." - else - let add_transaction ~must_replace = - (* Add the transaction to the pool *) - match Pool.add ~must_replace pool tx_raw transaction_object with - | Ok pool -> - let*! () = - Tx_pool_events.add_transaction - ~transaction: - (Ethereum_types.hash_to_string transaction_object.hash) - in - state.pool <- pool ; - return (Ok transaction_object.hash) - | Error msg -> return (Error msg) - in - let*! res_boundaries = - check_address_boundaries - ~pool - ~address:transaction_object.from - ~tx_pool_addr_limit - ~tx_pool_tx_per_addr_limit - in - match res_boundaries with - | Error `MaxUsers -> + let*! res_boundaries = + check_address_boundaries + ~pool + ~address:transaction_object.from + ~tx_pool_addr_limit + ~tx_pool_tx_per_addr_limit + in + match res_boundaries with + | Error `MaxUsers -> + return + (Error + "The transaction pool has reached its maximum threshold for user \ + transactions. Transaction is rejected.") + | Error (`MaxPerUser transactions) -> + let (Qty nonce) = transaction_object.nonce in + let max_nonce, nonce_exists = + Pool.Nonce_map.fold + (fun nonce' _tx (max_nonce, nonce_exists) -> + (Z.max max_nonce nonce', nonce_exists || nonce = nonce')) + transactions + (Z.zero, false) + in + if nonce_exists then + (* It must replace an existing one, otherwise it'll be above + the limit. *) + add_transaction ~must_replace:`Replace_existing + else if nonce < max_nonce then + (* If the nonce is smaller than the max nonce, we must shift the + list and drop one transaction. *) + add_transaction ~must_replace:(`Replace_shift max_nonce) + else + (* Otherwise we have just reached the limit of transactions. *) return (Error - "The transaction pool has reached its maximum threshold for user \ - transactions. Transaction is rejected.") - | Error (`MaxPerUser transactions) -> - let (Qty nonce) = transaction_object.nonce in - let max_nonce, nonce_exists = - Pool.Nonce_map.fold - (fun nonce' _tx (max_nonce, nonce_exists) -> - (Z.max max_nonce nonce', nonce_exists || nonce = nonce')) - transactions - (Z.zero, false) - in - if nonce_exists then - (* It must replace an existing one, otherwise it'll be above - the limit. *) - add_transaction ~must_replace:`Replace_existing - else if nonce < max_nonce then - (* If the nonce is smaller than the max nonce, we must shift the - list and drop one transaction. *) - add_transaction ~must_replace:(`Replace_shift max_nonce) - else - (* Otherwise we have just reached the limit of transactions. *) - return - (Error - "Limit of transaction for a user was reached. Transaction is \ - rejected.") - | Ok () -> add_transaction ~must_replace:`No + "Limit of transaction for a user was reached. Transaction is \ + rejected.") + | Ok () -> add_transaction ~must_replace:`No (** Checks that the transaction can be paid given the [gas_price] that was set and the current [base_fee_per_gas]. *) @@ -771,7 +748,6 @@ module Handlers = struct tx_timeout_limit; tx_pool_addr_limit; tx_pool_tx_per_addr_limit; - max_number_of_chunks; } : Types.parameters) = let state = @@ -785,7 +761,6 @@ module Handlers = struct tx_timeout_limit; tx_pool_addr_limit; tx_pool_tx_per_addr_limit; - max_number_of_chunks; locked = false; } in diff --git a/etherlink/bin_node/lib_dev/tx_pool.mli b/etherlink/bin_node/lib_dev/tx_pool.mli index c1c930395e06..67a717eaad73 100644 --- a/etherlink/bin_node/lib_dev/tx_pool.mli +++ b/etherlink/bin_node/lib_dev/tx_pool.mli @@ -28,9 +28,6 @@ type parameters = { tx_pool_addr_limit : int; (** Maximum allowed addresses inside the pool. *) tx_pool_tx_per_addr_limit : int; (** Maximum allowed transactions per address inside the pool. *) - max_number_of_chunks : int option; - (** Maximum allowed number of chunks to be sent (relevant for the - sequencer). *) } (** [start parameters] starts the tx-pool *) diff --git a/etherlink/bin_node/lib_dev/validate.ml b/etherlink/bin_node/lib_dev/validate.ml index 59628e2d25da..d2ab2dd0680b 100644 --- a/etherlink/bin_node/lib_dev/validate.ml +++ b/etherlink/bin_node/lib_dev/validate.ml @@ -92,11 +92,33 @@ let validate_balance_is_enough (transaction : Transaction.transaction) ~balance else if total_cost > balance then return (Error "Not enough funds") else return (Ok total_cost) -let validate_stateless ~next_nonce backend_rpc transaction ~caller = +let tx_data_size_limit_reached ~max_number_of_chunks ~tx_data = + let max_number_of_chunks = + Option.value + max_number_of_chunks + ~default:Sequencer_blueprint.maximum_chunks_per_l1_level + in + Bytes.length tx_data + > Sequencer_blueprint.maximum_usable_space_in_blueprint + (* Minus one so that the "rest" of the raw transaction can + be contained within one of the chunks. *) + (max_number_of_chunks - 1) + +let validate_tx_data_size ~max_number_of_chunks + (transaction : Transaction.transaction) = + let open Lwt_result_syntax in + let tx_data = transaction.data in + if tx_data_size_limit_reached ~max_number_of_chunks ~tx_data then + return @@ Error "Transaction data exceeded the allowed size." + else return (Ok ()) + +let validate_stateless ~next_nonce ~max_number_of_chunks backend_rpc transaction + ~caller = let open Lwt_result_syntax in let** () = validate_chain_id backend_rpc transaction in let** () = validate_nonce ~next_nonce transaction in let** () = validate_sender_not_a_contract backend_rpc caller in + let** () = validate_tx_data_size ~max_number_of_chunks transaction in return (Ok ()) let validate_balance_and_gas ~base_fee_per_gas ~maximum_gas_limit @@ -141,7 +163,7 @@ let validate_with_state_from_backend type validation_mode = Stateless | With_state | Full -let valid_transaction_object ~backend_rpc ~hash ~mode tx = +let valid_transaction_object ?max_number_of_chunks ~backend_rpc ~hash ~mode tx = let open Lwt_result_syntax in let**? tx_object = Transaction.to_transaction_object ~hash tx in let caller = tx_object.from in @@ -154,21 +176,35 @@ let valid_transaction_object ~backend_rpc ~hash ~mode tx = in let** () = match mode with - | Stateless -> validate_stateless backend_rpc ~next_nonce tx ~caller + | Stateless -> + validate_stateless + ~max_number_of_chunks + backend_rpc + ~next_nonce + tx + ~caller | With_state -> validate_with_state_from_backend backend_rpc tx ~caller | Full -> - let** () = validate_stateless ~next_nonce backend_rpc tx ~caller in + let** () = + validate_stateless + ~max_number_of_chunks + ~next_nonce + backend_rpc + tx + ~caller + in let** () = validate_with_state_from_backend backend_rpc tx ~caller in return (Ok ()) in return (Ok (next_nonce, tx_object)) -let is_tx_valid ((module Backend_rpc : Services_backend_sig.S) as backend_rpc) - ~mode tx_raw = +let is_tx_valid ?max_number_of_chunks + ((module Backend_rpc : Services_backend_sig.S) as backend_rpc) ~mode tx_raw + = let hash = Ethereum_types.hash_raw_tx tx_raw in let**? tx = Transaction.decode tx_raw in - valid_transaction_object ~backend_rpc ~hash ~mode tx + valid_transaction_object ?max_number_of_chunks ~backend_rpc ~hash ~mode tx type validation_config = { base_fee_per_gas : Ethereum_types.quantity; diff --git a/etherlink/bin_node/lib_dev/validate.mli b/etherlink/bin_node/lib_dev/validate.mli index e13e1e606086..57df71b57876 100644 --- a/etherlink/bin_node/lib_dev/validate.mli +++ b/etherlink/bin_node/lib_dev/validate.mli @@ -16,6 +16,7 @@ type validation_mode = [tx_raw] and returns the next allowed nonce for the sender of the transaction alongside the transaction object. *) val is_tx_valid : + ?max_number_of_chunks:int -> (module Services_backend_sig.S) -> mode:validation_mode -> string -> -- GitLab