From 094594e782d5c59cf5ac31157fd7779f4be30d6d Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 25 Mar 2024 14:44:02 +0100 Subject: [PATCH 1/7] EVM/Sequencer: add max number of chunks CLI arg --- etherlink/bin_node/config/configuration.ml | 35 ++++++++++++++++++--- etherlink/bin_node/config/configuration.mli | 4 +++ etherlink/bin_node/main.ml | 12 ++++++- etherlink/tezt/lib/evm_node.ml | 6 ++++ etherlink/tezt/lib/evm_node.mli | 1 + etherlink/tezt/tests/evm_rollup.ml | 3 ++ etherlink/tezt/tests/evm_sequencer.ml | 3 +- 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/etherlink/bin_node/config/configuration.ml b/etherlink/bin_node/config/configuration.ml index 3e4c075646ff..0899b1e6568b 100644 --- a/etherlink/bin_node/config/configuration.ml +++ b/etherlink/bin_node/config/configuration.ml @@ -22,6 +22,7 @@ type sequencer = { preimages : string; preimages_endpoint : Uri.t option; time_between_blocks : time_between_blocks; + max_number_of_chunks : int; private_rpc_port : int option; sequencer : Signature.public_key_hash; } @@ -74,6 +75,16 @@ let default_evm_node_endpoint = Uri.empty let default_time_between_blocks = Time_between_blocks 5. +let hard_maximum_number_of_chunks = + (* The kernel doesn't accept blueprints whose cumulated chunk size is higher + than 512kb. *) + let max_cumulated_chunks_size = 512 * 1024 in + (* External message size *) + let chunk_size = 4095 in + max_cumulated_chunks_size / chunk_size + +let default_max_number_of_chunks = hard_maximum_number_of_chunks + let log_filter_config_encoding : log_filter_config Data_encoding.t = let open Data_encoding in conv @@ -114,6 +125,7 @@ let encoding_sequencer = preimages_endpoint; rollup_node_endpoint; time_between_blocks; + max_number_of_chunks; private_rpc_port; sequencer; } -> @@ -121,12 +133,14 @@ let encoding_sequencer = preimages_endpoint, Uri.to_string rollup_node_endpoint, time_between_blocks, + max_number_of_chunks, private_rpc_port, sequencer )) (fun ( preimages, preimages_endpoint, rollup_node_endpoint, time_between_blocks, + max_number_of_chunks, private_rpc_port, sequencer ) -> { @@ -134,10 +148,11 @@ let encoding_sequencer = preimages_endpoint; rollup_node_endpoint = Uri.of_string rollup_node_endpoint; time_between_blocks; + max_number_of_chunks; private_rpc_port; sequencer; }) - (obj6 + (obj7 (dft "preimages" string default_preimages) (opt "preimages_endpoint" Tezos_rpc.Encoding.uri_encoding) (dft @@ -148,6 +163,7 @@ let encoding_sequencer = "time_between_blocks" encoding_time_between_blocks default_time_between_blocks) + (dft "max_number_of_chunks" int31 default_max_number_of_chunks) (opt "private-rpc-port" ~description:"RPC port for private server" @@ -296,7 +312,8 @@ module Cli = struct let create_sequencer ?private_rpc_port ~devmode ?rpc_addr ?rpc_port ?cors_origins ?cors_headers ?log_filter ?rollup_node_endpoint ?preimages - ?preimages_endpoint ?time_between_blocks ~sequencer = + ?preimages_endpoint ?time_between_blocks ?max_number_of_chunks ~sequencer + = let mode = { rollup_node_endpoint = @@ -307,6 +324,10 @@ module Cli = struct preimages_endpoint; time_between_blocks = Option.value ~default:default_time_between_blocks time_between_blocks; + max_number_of_chunks = + Option.value + ~default:default_max_number_of_chunks + max_number_of_chunks; private_rpc_port; sequencer; } @@ -375,7 +396,7 @@ module Cli = struct let patch_sequencer_configuration_from_args ?private_rpc_port ~devmode ?rpc_addr ?rpc_port ?cors_origins ?cors_headers ?log_filter ?rollup_node_endpoint ?preimages ?preimages_endpoint ?time_between_blocks - configuration ~sequencer = + ?max_number_of_chunks configuration ~sequencer = let mode = { rollup_node_endpoint = @@ -389,6 +410,10 @@ module Cli = struct Option.value ~default:configuration.mode.time_between_blocks time_between_blocks; + max_number_of_chunks = + Option.value + ~default:default_max_number_of_chunks + max_number_of_chunks; private_rpc_port; sequencer; } @@ -495,7 +520,7 @@ module Cli = struct let create_or_read_sequencer_config ~data_dir ~devmode ?rpc_addr ?rpc_port ?private_rpc_port ?cors_origins ?cors_headers ?log_filter ?rollup_node_endpoint ?preimages ?preimages_endpoint ?time_between_blocks - ~sequencer () = + ?max_number_of_chunks ~sequencer () = create_or_read_config ~data_dir ~devmode @@ -512,6 +537,7 @@ module Cli = struct ?preimages ?preimages_endpoint ?time_between_blocks + ?max_number_of_chunks ~sequencer) ~create: (create_sequencer @@ -519,6 +545,7 @@ module Cli = struct ?preimages ?preimages_endpoint ?time_between_blocks + ?max_number_of_chunks ~sequencer) () diff --git a/etherlink/bin_node/config/configuration.mli b/etherlink/bin_node/config/configuration.mli index 7ac03c3a16ba..09ad23786790 100644 --- a/etherlink/bin_node/config/configuration.mli +++ b/etherlink/bin_node/config/configuration.mli @@ -32,6 +32,8 @@ type sequencer = { preimages_endpoint : Uri.t option; (** Endpoint where pre-images can be fetched individually when missing. *) time_between_blocks : time_between_blocks; (** See {!time_between_blocks}. *) + max_number_of_chunks : int; + (** The maximum number of chunks per blueprints. *) private_rpc_port : int option; (** Port for internal RPC services *) sequencer : Signature.public_key_hash; (** The key used to sign the blueprints. *) @@ -108,6 +110,7 @@ module Cli : sig ?preimages:string -> ?preimages_endpoint:Uri.t -> ?time_between_blocks:time_between_blocks -> + ?max_number_of_chunks:int -> sequencer:Signature.public_key_hash -> unit -> sequencer t @@ -150,6 +153,7 @@ module Cli : sig ?preimages:string -> ?preimages_endpoint:Uri.t -> ?time_between_blocks:time_between_blocks -> + ?max_number_of_chunks:int -> sequencer:Signature.public_key_hash -> unit -> sequencer t tzresult Lwt.t diff --git a/etherlink/bin_node/main.ml b/etherlink/bin_node/main.ml index 95be4e2ea4bd..d7a7a441fcbe 100644 --- a/etherlink/bin_node/main.ml +++ b/etherlink/bin_node/main.ml @@ -475,6 +475,13 @@ let time_between_blocks_arg = ~placeholder:"10." Params.time_between_blocks +let max_number_of_chunks_arg = + Tezos_clic.arg + ~long:"max-number-of-chunks" + ~doc:"Maximum number of chunks per blueprint." + ~placeholder:"10." + Params.int + let keep_alive_arg = Tezos_clic.switch ~doc: @@ -655,7 +662,7 @@ let sequencer_command = let open Lwt_result_syntax in command ~desc:"Start the EVM node in sequencer mode" - (args18 + (args19 data_dir_arg rpc_addr_arg rpc_port_arg @@ -672,6 +679,7 @@ let sequencer_command = maximum_blueprints_ahead_arg maximum_blueprints_catchup_arg catchup_cooldown_arg + max_number_of_chunks_arg devmode_arg wallet_dir_arg) (prefixes ["run"; "sequencer"; "with"; "endpoint"] @@ -699,6 +707,7 @@ let sequencer_command = max_blueprints_ahead, max_blueprints_catchup, catchup_cooldown, + max_number_of_chunks, devmode, wallet_dir ) rollup_node_endpoint @@ -747,6 +756,7 @@ let sequencer_command = ?preimages ?preimages_endpoint ?time_between_blocks + ?max_number_of_chunks ~sequencer:sequencer_pkh () in diff --git a/etherlink/tezt/lib/evm_node.ml b/etherlink/tezt/lib/evm_node.ml index fc5930092a4f..253aa8ed84f2 100644 --- a/etherlink/tezt/lib/evm_node.ml +++ b/etherlink/tezt/lib/evm_node.ml @@ -44,6 +44,7 @@ type mode = max_blueprints_ahead : int option; max_blueprints_catchup : int option; catchup_cooldown : int option; + max_number_of_chunks : int option; devmode : bool; wallet_dir : string option; } @@ -435,6 +436,7 @@ let run_args evm_node = max_blueprints_ahead; max_blueprints_catchup; catchup_cooldown; + max_number_of_chunks; devmode; wallet_dir; } -> @@ -480,6 +482,10 @@ let run_args evm_node = (fun timestamp -> Client.time_of_timestamp timestamp |> Client.Time.to_notation) genesis_timestamp + @ Cli_arg.optional_arg + "max-number-of-chunks" + string_of_int + max_number_of_chunks @ Cli_arg.optional_switch "devmode" devmode @ Cli_arg.optional_arg "wallet-dir" Fun.id wallet_dir | Observer {preimages_dir; initial_kernel; rollup_node_endpoint} -> diff --git a/etherlink/tezt/lib/evm_node.mli b/etherlink/tezt/lib/evm_node.mli index 65146448b606..ca61f0db5ef5 100644 --- a/etherlink/tezt/lib/evm_node.mli +++ b/etherlink/tezt/lib/evm_node.mli @@ -56,6 +56,7 @@ type mode = max_blueprints_ahead : int option; max_blueprints_catchup : int option; catchup_cooldown : int option; + max_number_of_chunks : int option; devmode : bool; (** --devmode flag. *) wallet_dir : string option; (** --wallet-dir: client directory. *) } diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index ee22c08ecc1e..5e98ed850a15 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -478,6 +478,7 @@ let setup_evm_kernel ?(setup_kernel_root_hash = true) ?config max_blueprints_ahead = None; max_blueprints_catchup = None; catchup_cooldown = None; + max_number_of_chunks = None; devmode; wallet_dir = Some (Client.base_dir client); }) @@ -4587,6 +4588,7 @@ let test_migrate_proxy_to_sequencer_future = max_blueprints_ahead = None; max_blueprints_catchup = None; catchup_cooldown = None; + max_number_of_chunks = None; devmode = true; wallet_dir = Some (Client.base_dir client); } @@ -4748,6 +4750,7 @@ let test_migrate_proxy_to_sequencer_past = max_blueprints_ahead = None; max_blueprints_catchup = None; catchup_cooldown = None; + max_number_of_chunks = None; devmode = true; wallet_dir = Some (Client.base_dir client); } diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index f2e9e92dcce6..ecca00053096 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -132,7 +132,7 @@ let setup_l1_contracts ?(dictator = Constant.bootstrap2) client = let setup_sequencer ?(devmode = true) ?config ?genesis_timestamp ?time_between_blocks ?max_blueprints_lag ?max_blueprints_ahead ?max_blueprints_catchup ?catchup_cooldown ?delayed_inbox_timeout - ?delayed_inbox_min_levels + ?delayed_inbox_min_levels ?max_number_of_chunks ?(bootstrap_accounts = Eth_account.bootstrap_accounts) ?(sequencer = Constant.bootstrap1) ?(kernel = Constant.WASM.evm_kernel) ?da_fee ?minimum_base_fee_per_gas ?preimages_dir protocol = @@ -199,6 +199,7 @@ let setup_sequencer ?(devmode = true) ?config ?genesis_timestamp max_blueprints_ahead; max_blueprints_catchup; catchup_cooldown; + max_number_of_chunks; devmode; wallet_dir = Some (Client.base_dir client); } -- GitLab From cf96ebbd9acee7cb3a0ffa86e4cca64008922fa5 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 25 Mar 2024 16:16:23 +0100 Subject: [PATCH 2/7] EVM/Sequencer: tx pool pops transactions until a given size limit --- etherlink/bin_node/lib_dev/block_producer.ml | 19 ++++- etherlink/bin_node/lib_dev/block_producer.mli | 1 + etherlink/bin_node/lib_dev/sequencer.ml | 7 +- .../bin_node/lib_dev/sequencer_blueprint.ml | 5 ++ .../bin_node/lib_dev/sequencer_blueprint.mli | 8 ++ etherlink/bin_node/lib_dev/tx_pool.ml | 75 ++++++++++++++----- etherlink/bin_node/lib_dev/tx_pool.mli | 10 ++- 7 files changed, 98 insertions(+), 27 deletions(-) diff --git a/etherlink/bin_node/lib_dev/block_producer.ml b/etherlink/bin_node/lib_dev/block_producer.ml index 743ed14aab6a..77ce164821f8 100644 --- a/etherlink/bin_node/lib_dev/block_producer.ml +++ b/etherlink/bin_node/lib_dev/block_producer.ml @@ -9,6 +9,7 @@ type parameters = { cctxt : Client_context.wallet; smart_rollup_address : string; sequencer_key : Client_keys.sk_uri; + maximum_number_of_chunks : int; } module Types = struct @@ -75,9 +76,13 @@ let get_hashes ~transactions ~delayed_transactions = return (delayed_transactions @ hashes) let produce_block ~cctxt ~smart_rollup_address ~sequencer_key ~force ~timestamp - = + ~maximum_number_of_chunks = let open Lwt_result_syntax in - let* tx_pool_response = Tx_pool.pop_transactions () in + let maximum_cumulative_size = + Sequencer_blueprint.maximum_usable_space_in_blueprint + maximum_number_of_chunks + in + let* tx_pool_response = Tx_pool.pop_transactions ~maximum_cumulative_size in match tx_pool_response with | Transactions transactions -> let* delayed_transactions = Evm_context.delayed_inbox_hashes () in @@ -132,13 +137,21 @@ module Handlers = struct match request with | Request.Produce_block (timestamp, force) -> protect @@ fun () -> - let {cctxt; smart_rollup_address; sequencer_key} = state in + let { + cctxt; + smart_rollup_address; + sequencer_key; + maximum_number_of_chunks; + } = + state + in produce_block ~cctxt ~smart_rollup_address ~sequencer_key ~force ~timestamp + ~maximum_number_of_chunks type launch_error = error trace diff --git a/etherlink/bin_node/lib_dev/block_producer.mli b/etherlink/bin_node/lib_dev/block_producer.mli index 06e4e4243e66..457eaf7add09 100644 --- a/etherlink/bin_node/lib_dev/block_producer.mli +++ b/etherlink/bin_node/lib_dev/block_producer.mli @@ -9,6 +9,7 @@ type parameters = { cctxt : Client_context.wallet; smart_rollup_address : string; sequencer_key : Client_keys.sk_uri; + maximum_number_of_chunks : int; } (** [start parameters] starts the events follower. *) diff --git a/etherlink/bin_node/lib_dev/sequencer.ml b/etherlink/bin_node/lib_dev/sequencer.ml index 5a98de78063d..f819c2dc590e 100644 --- a/etherlink/bin_node/lib_dev/sequencer.ml +++ b/etherlink/bin_node/lib_dev/sequencer.ml @@ -242,7 +242,12 @@ let main ~data_dir ~rollup_node_endpoint ~max_blueprints_lag in let* () = Block_producer.start - {cctxt; smart_rollup_address; sequencer_key = sequencer} + { + cctxt; + smart_rollup_address; + sequencer_key = sequencer; + maximum_number_of_chunks = configuration.mode.max_number_of_chunks; + } in let* () = Evm_events_follower.start diff --git a/etherlink/bin_node/lib_dev/sequencer_blueprint.ml b/etherlink/bin_node/lib_dev/sequencer_blueprint.ml index b136a4fcce9b..be819f065748 100644 --- a/etherlink/bin_node/lib_dev/sequencer_blueprint.ml +++ b/etherlink/bin_node/lib_dev/sequencer_blueprint.ml @@ -41,6 +41,11 @@ let max_chunk_size = - blueprint_tag_size - blueprint_number_size - nb_chunks_size - chunk_index_size - rlp_tags_size - signature_size +let maximum_usable_space_in_blueprint chunks_count = + chunks_count * max_chunk_size + +let maximum_chunks_per_l1_level = 512 * 1024 / 4096 + let encode_transaction raw = let open Rlp in Value (Bytes.of_string raw) diff --git a/etherlink/bin_node/lib_dev/sequencer_blueprint.mli b/etherlink/bin_node/lib_dev/sequencer_blueprint.mli index bafec7020a90..78fc1777a3c2 100644 --- a/etherlink/bin_node/lib_dev/sequencer_blueprint.mli +++ b/etherlink/bin_node/lib_dev/sequencer_blueprint.mli @@ -23,3 +23,11 @@ val create : delayed_transactions:Ethereum_types.hash list -> transactions:string list -> t tzresult Lwt.t + +(** [maximum_usable_size_in_blueprint chunks_count] returns the available space + for transactions in a blueprint composed of [chunks_count] chunks. *) +val maximum_usable_space_in_blueprint : int -> int + +(* [maximum_chunks_per_l1_level] is the maximum number of chunks a L1 block can + hold at once. *) +val maximum_chunks_per_l1_level : int diff --git a/etherlink/bin_node/lib_dev/tx_pool.ml b/etherlink/bin_node/lib_dev/tx_pool.ml index 00ca1c361423..fc66163f8213 100644 --- a/etherlink/bin_node/lib_dev/tx_pool.ml +++ b/etherlink/bin_node/lib_dev/tx_pool.ml @@ -147,7 +147,7 @@ module Request = struct | Add_transaction : string -> ((Ethereum_types.hash, string) result, tztrace) t - | Pop_transactions : (popped_transactions, tztrace) t + | Pop_transactions : int -> (popped_transactions, tztrace) t | Pop_and_inject_transactions : (unit, tztrace) t | Lock_transactions : (unit, tztrace) t | Unlock_transactions : (unit, tztrace) t @@ -173,9 +173,15 @@ module Request = struct case (Tag 1) ~title:"Pop_transactions" - (obj1 (req "request" (constant "pop_transactions"))) - (function View Pop_transactions -> Some () | _ -> None) - (fun () -> View Pop_transactions); + (obj2 + (req "request" (constant "pop_transactions")) + (req "maximum_cumulatize_size" int31)) + (function + | View (Pop_transactions maximum_cumulative_size) -> + Some ((), maximum_cumulative_size) + | _ -> None) + (fun ((), maximum_cumulative_size) -> + View (Pop_transactions maximum_cumulative_size)); case (Tag 2) ~title:"Pop_and_inject_transactions" @@ -203,7 +209,11 @@ module Request = struct ppf "Add tx [%s] to tx-pool" (Hex.of_string tx_raw |> Hex.show) - | Pop_transactions -> Format.fprintf ppf "Popping transactions" + | Pop_transactions maximum_cumulative_size -> + Format.fprintf + ppf + "Popping transactions of maximum cumulative size %d bytes" + maximum_cumulative_size | Pop_and_inject_transactions -> Format.fprintf ppf "Popping and injecting transactions" | Lock_transactions -> Format.fprintf ppf "Locking the transactions" @@ -251,7 +261,7 @@ let can_prepay ~balance ~gas_price ~gas_limit = let can_pay_with_current_base_fee ~gas_price ~base_fee_per_gas = gas_price >= base_fee_per_gas -let pop_transactions state = +let pop_transactions state ~maximum_cumulative_size = let open Lwt_result_syntax in let Types. { @@ -292,24 +302,40 @@ let pop_transactions state = pool) pool in - (* Select transaction with nonce equal to user's nonce and that - can be prepaid. + (* Select transaction with nonce equal to user's nonce, that can be prepaid + and selects a sum of transactions that wouldn't go above the size limit + of the blueprint. Also removes the transactions from the pool. *) - let txs, pool = + let txs, pool, _ = addr_with_nonces |> List.fold_left - (fun (txs, pool) (pkey, _, current_nonce) -> + (fun (txs, pool, cumulative_size) (pkey, _, current_nonce) -> + (* This mutable counter is purely local and used only for the + partition. *) + let accumulated_size = ref cumulative_size in let selected, pool = Pool.partition pkey - (fun nonce {gas_price; _} -> - nonce = current_nonce - && can_pay_with_current_base_fee ~gas_price ~base_fee_per_gas) + (fun nonce {gas_price; raw_tx; _} -> + let check_nonce = nonce = current_nonce in + let can_fit = + !accumulated_size + String.length raw_tx + <= maximum_cumulative_size + in + let can_pay = + can_pay_with_current_base_fee ~gas_price ~base_fee_per_gas + in + let selected = check_nonce && can_pay && can_fit in + (* If the transaction is selected, this means it will fit *) + if selected then + accumulated_size := + !accumulated_size + String.length raw_tx ; + selected) pool in let txs = List.append txs selected in - (txs, pool)) - ([], pool) + (txs, pool, !accumulated_size)) + ([], pool, 0) in (* Sorting transactions by index. First tx in the pool is the first tx to be sent to the batcher. *) @@ -331,7 +357,15 @@ let pop_and_inject_transactions state = failwith "Internal error: the sequencer is not supposed to use this function" | Observer | Proxy _ -> ( - let* res = pop_transactions state in + (* We over approximate the number of transactions to pop in proxy and + observer mode to the maximum size an L1 block can hold. If the proxy + sends more, they won't be applied at the next level. For the observer, + it prevents spamming the sequencer. *) + let maximum_cumulative_size = + Sequencer_blueprint.maximum_usable_space_in_blueprint + Sequencer_blueprint.maximum_chunks_per_l1_level + in + let* res = pop_transactions state ~maximum_cumulative_size in match res with | Locked -> return_unit | Transactions txs -> @@ -391,7 +425,8 @@ module Handlers = struct let* res = on_normal_transaction state transaction in let* () = observer_self_inject_request w in return res - | Request.Pop_transactions -> protect @@ fun () -> pop_transactions state + | Request.Pop_transactions maximum_cumulative_size -> + protect @@ fun () -> pop_transactions state ~maximum_cumulative_size | Request.Pop_and_inject_transactions -> protect @@ fun () -> pop_and_inject_transactions state | Request.Lock_transactions -> @@ -490,10 +525,12 @@ let nonce pkey = in return next_nonce -let pop_transactions () = +let pop_transactions ~maximum_cumulative_size = let open Lwt_result_syntax in let*? worker = Lazy.force worker in - Worker.Queue.push_request_and_wait worker Request.Pop_transactions + Worker.Queue.push_request_and_wait + worker + (Request.Pop_transactions maximum_cumulative_size) |> handle_request_error let pop_and_inject_transactions () = diff --git a/etherlink/bin_node/lib_dev/tx_pool.mli b/etherlink/bin_node/lib_dev/tx_pool.mli index 374e6f01251e..6f0a0b6e3a8a 100644 --- a/etherlink/bin_node/lib_dev/tx_pool.mli +++ b/etherlink/bin_node/lib_dev/tx_pool.mli @@ -26,15 +26,17 @@ val shutdown : unit -> unit tzresult Lwt.t val add : string -> (Ethereum_types.hash, string) result tzresult Lwt.t (** [nonce address] returns the nonce of the user - Returns the first gap in the tx-pool, or the nonce stored on the rollup + Returns the first gap in the tx-pool, or the nonce stored on the rollup if no transactions are in the pool. *) val nonce : Ethereum_types.Address.t -> Ethereum_types.quantity tzresult Lwt.t -(** [pop_transactions ()] pops the valid transactions from the pool. *) -val pop_transactions : unit -> popped_transactions tzresult Lwt.t +(** [pop_transactions maximum_cumulative_size] pops as much valid transactions + as possible from the pool, until their cumulative size exceeds + `maximum_cumulative_size`. *) +val pop_transactions : maximum_cumulative_size:int -> string list tzresult Lwt.t (** [pop_and_inject_transactions ()] pops the valid transactions from - the pool using {!pop_transactions }and injects them using + the pool using {!pop_transactions} and injects them using [inject_raw_transactions] provided by {!parameters.rollup_node}. *) val pop_and_inject_transactions : unit -> unit tzresult Lwt.t -- GitLab From 2596aa30b6df532d613c1d2f464a9b8bd7e08e65 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 26 Mar 2024 11:23:25 +0100 Subject: [PATCH 3/7] EVM/Sequencer: is_locked request for txpool --- etherlink/bin_node/lib_dev/tx_pool.ml | 17 +++++++++++++++++ etherlink/bin_node/lib_dev/tx_pool.mli | 3 +++ 2 files changed, 20 insertions(+) diff --git a/etherlink/bin_node/lib_dev/tx_pool.ml b/etherlink/bin_node/lib_dev/tx_pool.ml index fc66163f8213..f7cf6ee294e4 100644 --- a/etherlink/bin_node/lib_dev/tx_pool.ml +++ b/etherlink/bin_node/lib_dev/tx_pool.ml @@ -151,6 +151,7 @@ module Request = struct | Pop_and_inject_transactions : (unit, tztrace) t | Lock_transactions : (unit, tztrace) t | Unlock_transactions : (unit, tztrace) t + | Is_locked : (bool, tztrace) t type view = View : _ t -> view @@ -200,6 +201,12 @@ module Request = struct (obj1 (req "request" (constant "unlock_transactions"))) (function View Unlock_transactions -> Some () | _ -> None) (fun () -> View Unlock_transactions); + case + (Tag 5) + ~title:"Is_locked" + (obj1 (req "request" (constant "is_locked"))) + (function View Is_locked -> Some () | _ -> None) + (fun () -> View Is_locked); ] let pp ppf (View r) = @@ -218,6 +225,7 @@ module Request = struct Format.fprintf ppf "Popping and injecting transactions" | Lock_transactions -> Format.fprintf ppf "Locking the transactions" | Unlock_transactions -> Format.fprintf ppf "Unlocking the transactions" + | Is_locked -> Format.fprintf ppf "Checking if the tx pool is locked" end module Worker = Worker.MakeSingle (Name) (Request) (Types) @@ -398,6 +406,8 @@ let lock_transactions state = state.Types.locked <- true let unlock_transactions state = state.Types.locked <- false +let is_locked state = state.Types.locked + module Handlers = struct type self = worker @@ -432,6 +442,7 @@ module Handlers = struct | Request.Lock_transactions -> protect @@ fun () -> return (lock_transactions state) | Request.Unlock_transactions -> return (unlock_transactions state) + | Request.Is_locked -> protect @@ fun () -> return (is_locked state) type launch_error = error trace @@ -558,3 +569,9 @@ let unlock_transactions () = let*? worker = Lazy.force worker in Worker.Queue.push_request_and_wait worker Request.Unlock_transactions |> handle_request_error + +let is_locked () = + let open Lwt_result_syntax in + let*? worker = Lazy.force worker in + Worker.Queue.push_request_and_wait worker Request.Is_locked + |> handle_request_error diff --git a/etherlink/bin_node/lib_dev/tx_pool.mli b/etherlink/bin_node/lib_dev/tx_pool.mli index 6f0a0b6e3a8a..c764e3ff790b 100644 --- a/etherlink/bin_node/lib_dev/tx_pool.mli +++ b/etherlink/bin_node/lib_dev/tx_pool.mli @@ -52,3 +52,6 @@ val lock_transactions : unit -> unit tzresult Lwt.t (** [unlock_transactions] unlocks the transactions if it was locked by {!lock_transactions}. *) val unlock_transactions : unit -> unit tzresult Lwt.t + +(** [is_locked] checks if the pools is locked. *) +val is_locked : unit -> bool tzresult Lwt.t -- GitLab From b787489fdb98c7dc4eb73f67202e23b465a5b572 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 26 Mar 2024 11:50:36 +0100 Subject: [PATCH 4/7] EVM/Sequencer: separate locking and pop transactions logic --- etherlink/bin_node/lib_dev/block_producer.ml | 78 ++++++++++---------- etherlink/bin_node/lib_dev/tx_pool.ml | 59 +++++++-------- etherlink/bin_node/lib_dev/tx_pool.mli | 4 +- 3 files changed, 65 insertions(+), 76 deletions(-) diff --git a/etherlink/bin_node/lib_dev/block_producer.ml b/etherlink/bin_node/lib_dev/block_producer.ml index 77ce164821f8..d491054ada47 100644 --- a/etherlink/bin_node/lib_dev/block_producer.ml +++ b/etherlink/bin_node/lib_dev/block_producer.ml @@ -82,48 +82,46 @@ let produce_block ~cctxt ~smart_rollup_address ~sequencer_key ~force ~timestamp Sequencer_blueprint.maximum_usable_space_in_blueprint maximum_number_of_chunks in - let* tx_pool_response = Tx_pool.pop_transactions ~maximum_cumulative_size in - match tx_pool_response with - | Transactions transactions -> - let* delayed_transactions = Evm_context.delayed_inbox_hashes () in - let n = List.length transactions + List.length delayed_transactions in - if force || n > 0 then - let*! head_info = Evm_context.head_info () in + let* is_locked = Tx_pool.is_locked () in + if is_locked then + let*! () = Block_producer_events.production_locked () in + return 0 + else + let* transactions = Tx_pool.pop_transactions ~maximum_cumulative_size in + let* delayed_transactions = Evm_context.delayed_inbox_hashes () in + let n = List.length transactions + List.length delayed_transactions in + if force || n > 0 then + let*! head_info = Evm_context.head_info () in + Helpers.with_timing + (Blueprint_events.blueprint_production head_info.next_blueprint_number) + @@ fun () -> + let*? hashes = get_hashes ~transactions ~delayed_transactions in + let* blueprint = Helpers.with_timing - (Blueprint_events.blueprint_production - head_info.next_blueprint_number) + (Blueprint_events.blueprint_proposal head_info.next_blueprint_number) @@ fun () -> - let*? hashes = get_hashes ~transactions ~delayed_transactions in - let* blueprint = - Helpers.with_timing - (Blueprint_events.blueprint_proposal - head_info.next_blueprint_number) - @@ fun () -> - Sequencer_blueprint.create - ~sequencer_key - ~cctxt - ~timestamp - ~smart_rollup_address - ~transactions - ~delayed_transactions - ~parent_hash:head_info.current_block_hash - ~number:head_info.next_blueprint_number - in - let* () = - Evm_context.apply_blueprint timestamp blueprint delayed_transactions - in - let (Qty number) = head_info.next_blueprint_number in - let* () = Blueprints_publisher.publish number blueprint in - let*! () = - List.iter_p - (fun hash -> Block_producer_events.transaction_selected ~hash) - hashes - in - return n - else return 0 - | Locked -> - let*! () = Block_producer_events.production_locked () in - return 0 + Sequencer_blueprint.create + ~sequencer_key + ~cctxt + ~timestamp + ~smart_rollup_address + ~transactions + ~delayed_transactions + ~parent_hash:head_info.current_block_hash + ~number:head_info.next_blueprint_number + in + let* () = + Evm_context.apply_blueprint timestamp blueprint delayed_transactions + in + let (Qty number) = head_info.next_blueprint_number in + let* () = Blueprints_publisher.publish number blueprint in + let*! () = + List.iter_p + (fun hash -> Block_producer_events.transaction_selected ~hash) + hashes + in + return n + else return 0 module Handlers = struct type self = worker diff --git a/etherlink/bin_node/lib_dev/tx_pool.ml b/etherlink/bin_node/lib_dev/tx_pool.ml index f7cf6ee294e4..8405d0ce7c98 100644 --- a/etherlink/bin_node/lib_dev/tx_pool.ml +++ b/etherlink/bin_node/lib_dev/tx_pool.ml @@ -115,8 +115,6 @@ type parameters = { mode : mode; } -type popped_transactions = Locked | Transactions of string list - module Types = struct type state = { rollup_node : (module Services_backend_sig.S); @@ -147,7 +145,7 @@ module Request = struct | Add_transaction : string -> ((Ethereum_types.hash, string) result, tztrace) t - | Pop_transactions : int -> (popped_transactions, tztrace) t + | Pop_transactions : int -> (string list, tztrace) t | Pop_and_inject_transactions : (unit, tztrace) t | Lock_transactions : (unit, tztrace) t | Unlock_transactions : (unit, tztrace) t @@ -280,7 +278,7 @@ let pop_transactions state ~maximum_cumulative_size = } = state in - if locked then return Locked + if locked then return [] else (* Get all the addresses in the tx-pool. *) let addresses = Pool.addresses pool in @@ -355,7 +353,7 @@ let pop_transactions state ~maximum_cumulative_size = in (* update the pool *) state.pool <- pool ; - return (Transactions txs) + return txs let pop_and_inject_transactions state = let open Lwt_result_syntax in @@ -364,7 +362,7 @@ let pop_and_inject_transactions state = | Sequencer -> failwith "Internal error: the sequencer is not supposed to use this function" - | Observer | Proxy _ -> ( + | Observer | Proxy _ -> (* We over approximate the number of transactions to pop in proxy and observer mode to the maximum size an L1 block can hold. If the proxy sends more, they won't be applied at the next level. For the observer, @@ -373,34 +371,29 @@ let pop_and_inject_transactions state = Sequencer_blueprint.maximum_usable_space_in_blueprint Sequencer_blueprint.maximum_chunks_per_l1_level in - let* res = pop_transactions state ~maximum_cumulative_size in - match res with - | Locked -> return_unit - | Transactions txs -> - if not (List.is_empty txs) then - let (module Rollup_node : Services_backend_sig.S) = - state.rollup_node - in - let*! hashes = - Rollup_node.inject_raw_transactions - (* The timestamp is ignored in observer and proxy mode, it's just for - compatibility with sequencer mode. *) - ~timestamp:(Helpers.now ()) - ~smart_rollup_address:state.smart_rollup_address - ~transactions:txs + let* txs = pop_transactions state ~maximum_cumulative_size in + if not (List.is_empty txs) then + let (module Rollup_node : Services_backend_sig.S) = state.rollup_node in + let*! hashes = + Rollup_node.inject_raw_transactions + (* The timestamp is ignored in observer and proxy mode, it's just for + compatibility with sequencer mode. *) + ~timestamp:(Helpers.now ()) + ~smart_rollup_address:state.smart_rollup_address + ~transactions:txs + in + match hashes with + | Error trace -> + let*! () = Tx_pool_events.transaction_injection_failed trace in + return_unit + | Ok hashes -> + let*! () = + List.iter_s + (fun hash -> Tx_pool_events.transaction_injected ~hash) + hashes in - match hashes with - | Error trace -> - let*! () = Tx_pool_events.transaction_injection_failed trace in - return_unit - | Ok hashes -> - let*! () = - List.iter_s - (fun hash -> Tx_pool_events.transaction_injected ~hash) - hashes - in - return_unit - else return_unit) + return_unit + else return_unit let lock_transactions state = state.Types.locked <- true diff --git a/etherlink/bin_node/lib_dev/tx_pool.mli b/etherlink/bin_node/lib_dev/tx_pool.mli index c764e3ff790b..b9ea9394a968 100644 --- a/etherlink/bin_node/lib_dev/tx_pool.mli +++ b/etherlink/bin_node/lib_dev/tx_pool.mli @@ -13,8 +13,6 @@ type parameters = { mode : mode; } -type popped_transactions = Locked | Transactions of string list - (** [start parameters] starts the tx-pool *) val start : parameters -> unit tzresult Lwt.t @@ -32,7 +30,7 @@ val nonce : Ethereum_types.Address.t -> Ethereum_types.quantity tzresult Lwt.t (** [pop_transactions maximum_cumulative_size] pops as much valid transactions as possible from the pool, until their cumulative size exceeds - `maximum_cumulative_size`. *) + `maximum_cumulative_size`. Returns no transactions if the pool is locked. *) val pop_transactions : maximum_cumulative_size:int -> string list tzresult Lwt.t (** [pop_and_inject_transactions ()] pops the valid transactions from -- GitLab From 14a72a0a17dcb15dcb797c921c3b1bb07c600f01 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 26 Mar 2024 14:40:30 +0100 Subject: [PATCH 5/7] EVM/Sequencer: count delayed transaction in block size --- etherlink/bin_node/lib_dev/block_producer.ml | 77 +++++++++++++++++++- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/etherlink/bin_node/lib_dev/block_producer.ml b/etherlink/bin_node/lib_dev/block_producer.ml index d491054ada47..ee0da822950d 100644 --- a/etherlink/bin_node/lib_dev/block_producer.ml +++ b/etherlink/bin_node/lib_dev/block_producer.ml @@ -12,6 +12,51 @@ type parameters = { maximum_number_of_chunks : int; } +(* The size of a delayed transaction is overapproximated to the maximum size + of an inbox message, as chunks are not supported in the delayed bridge. *) +let maximum_delayed_transaction_size = 4096 + +(* + The legacy transactions are as follows: + ----------------------------- + | Nonce | Up to 32 bytes | + ----------------------------- + | GasPrice | Up to 32 bytes | + ----------------------------- + | GasLimit | Up to 32 bytes | + ----------------------------- + | To | 20 bytes addr | + ----------------------------- + | Value | Up to 32 bytes | + ----------------------------- + | Data | 0 - unlimited | + ----------------------------- + | V | 1 (usually) | + ----------------------------- + | R | 32 bytes | + ----------------------------- + | S | 32 bytes | + ----------------------------- + + where `up to` start at 0, and encoded as the empty byte for the 0 value + according to RLP specification. +*) +let minimum_ethereum_transaction_size = + Rlp.( + List + [ + Value Bytes.empty; + Value Bytes.empty; + Value Bytes.empty; + Value (Bytes.make 20 '\000'); + Value Bytes.empty; + Value Bytes.empty; + Value Bytes.empty; + Value (Bytes.make 32 '\000'); + Value (Bytes.make 32 '\000'); + ] + |> encode |> Bytes.length) + module Types = struct type nonrec parameters = parameters @@ -75,20 +120,44 @@ let get_hashes ~transactions ~delayed_transactions = in return (delayed_transactions @ hashes) -let produce_block ~cctxt ~smart_rollup_address ~sequencer_key ~force ~timestamp - ~maximum_number_of_chunks = +let take_delayed_transactions maximum_number_of_chunks = let open Lwt_result_syntax in let maximum_cumulative_size = Sequencer_blueprint.maximum_usable_space_in_blueprint maximum_number_of_chunks in + let maximum_delayed_transactions = + maximum_cumulative_size / maximum_delayed_transaction_size + in + let* delayed_transactions = Evm_context.delayed_inbox_hashes () in + let delayed_transactions = + List.take_n maximum_delayed_transactions delayed_transactions + in + let remaining_cumulative_size = + maximum_cumulative_size - (List.length delayed_transactions * 4096) + in + return (delayed_transactions, remaining_cumulative_size) + +let produce_block ~cctxt ~smart_rollup_address ~sequencer_key ~force ~timestamp + ~maximum_number_of_chunks = + let open Lwt_result_syntax in let* is_locked = Tx_pool.is_locked () in if is_locked then let*! () = Block_producer_events.production_locked () in return 0 else - let* transactions = Tx_pool.pop_transactions ~maximum_cumulative_size in - let* delayed_transactions = Evm_context.delayed_inbox_hashes () in + let* delayed_transactions, remaining_cumulative_size = + take_delayed_transactions maximum_number_of_chunks + in + let* transactions = + (* Low key optimization to avoid even checking the txpool if there is not + enough space for the smallest transaction. *) + if remaining_cumulative_size <= minimum_ethereum_transaction_size then + return [] + else + Tx_pool.pop_transactions + ~maximum_cumulative_size:remaining_cumulative_size + in let n = List.length transactions + List.length delayed_transactions in if force || n > 0 then let*! head_info = Evm_context.head_info () in -- GitLab From cc1b4a7adb97a7ee08b1568893dfcfd927dbafdb Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 26 Mar 2024 19:13:12 +0100 Subject: [PATCH 6/7] EVM/Tezt: move batching helper to Helper module --- etherlink/tezt/lib/helpers.ml | 76 ++++++++++++++++++++++++++++++ etherlink/tezt/lib/helpers.mli | 38 +++++++++++++++ etherlink/tezt/tests/evm_rollup.ml | 74 +---------------------------- 3 files changed, 115 insertions(+), 73 deletions(-) diff --git a/etherlink/tezt/lib/helpers.ml b/etherlink/tezt/lib/helpers.ml index 3175db8b1f59..d79fc5fe336c 100644 --- a/etherlink/tezt/lib/helpers.ml +++ b/etherlink/tezt/lib/helpers.ml @@ -182,3 +182,79 @@ let bake_until_sync ?(timeout = 30.) ~sc_rollup_node ~proxy ~sequencer ~client go () in Lwt.pick [go (); Lwt_unix.sleep timeout] + +(** [wait_for_transaction_receipt ~evm_node ~transaction_hash] takes an + transaction_hash and returns only when the receipt is non null, or [count] + blocks have passed and the receipt is still not available. *) +let wait_for_transaction_receipt ?(count = 3) ~evm_node ~transaction_hash () = + let rec loop count = + let* () = Lwt_unix.sleep 5. in + let* receipt = + Evm_node.( + call_evm_rpc + evm_node + { + method_ = "eth_getTransactionReceipt"; + parameters = `A [`String transaction_hash]; + }) + in + if receipt |> Evm_node.extract_result |> JSON.is_null then + if count > 0 then loop (count - 1) + else Test.fail "Transaction still hasn't been included" + else + receipt |> Evm_node.extract_result + |> Transaction.transaction_receipt_of_json |> return + in + loop count + +let wait_for_application ~evm_node ~sc_rollup_node ~client apply = + let max_iteration = 10 in + let application_result = apply () in + let rec loop current_iteration = + let* () = Lwt_unix.sleep 5. in + let* () = next_evm_level ~evm_node ~sc_rollup_node ~client in + if max_iteration < current_iteration then + Test.fail + "Baked more than %d blocks and the operation's application is still \ + pending" + max_iteration ; + if Lwt.state application_result = Lwt.Sleep then loop (current_iteration + 1) + else unit + in + (* Using [Lwt.both] ensures that any exception thrown in [tx_hash] will be + thrown by [Lwt.both] as well. *) + let* result, () = Lwt.both application_result (loop 0) in + return result + +let batch_n_transactions ~evm_node txs = + let requests = + List.map + (fun tx -> + Evm_node. + {method_ = "eth_sendRawTransaction"; parameters = `A [`String tx]}) + txs + in + let* hashes = Evm_node.batch_evm_rpc evm_node requests in + let hashes = + hashes |> JSON.as_list + |> List.map (fun json -> Evm_node.extract_result json |> JSON.as_string) + in + return (requests, hashes) + +(* sending more than ~300 tx could fail, because or curl *) +let send_n_transactions ~sc_rollup_node ~client ~evm_node ?wait_for_blocks txs = + let* requests, hashes = batch_n_transactions ~evm_node txs in + let first_hash = List.hd hashes in + (* Let's wait until one of the transactions is injected into a block, and + test this block contains the `n` transactions as expected. *) + let* receipt = + wait_for_application + ~evm_node + ~sc_rollup_node + ~client + (wait_for_transaction_receipt + ?count:wait_for_blocks + ~evm_node + ~transaction_hash:first_hash) + in + return (requests, receipt, hashes) diff --git a/etherlink/tezt/lib/helpers.mli b/etherlink/tezt/lib/helpers.mli index 0726ae366c59..83b6d7cd6092 100644 --- a/etherlink/tezt/lib/helpers.mli +++ b/etherlink/tezt/lib/helpers.mli @@ -129,3 +129,41 @@ val bake_until_sync : client:Client.t -> unit -> unit Lwt.t + +(** [wait_for_transaction_receipt ?count ~evm_node ~transaction_hash ()] takes a + transaction_hash and returns only when the receipt is non null, or [count] + blocks have passed and the receipt is still not available. *) +val wait_for_transaction_receipt : + ?count:int -> + evm_node:Evm_node.t -> + transaction_hash:string -> + unit -> + Transaction.transaction_receipt Lwt.t + +(** [wait_for_application ~evm_node ~sc_rollup_node ~client apply] returns only + when the `apply` yields, or fails when 10 blocks have passed. *) +val wait_for_application : + evm_node:Evm_node.t -> + sc_rollup_node:Sc_rollup_node.t -> + client:Client.t -> + (unit -> 'a Lwt.t) -> + 'a Lwt.t + +(** [batch_n_transactions ~evm_node raw_transactions] batches [raw_transactions] + to the [evm_node] and returns the requests and transaction hashes. *) +val batch_n_transactions : + evm_node:Evm_node.t -> + string list -> + (Evm_node.request list * string list) Lwt.t + +(** [send_n_transactions ~sc_rollup_node ~evm_node ?wait_for_blocks + raw_transactions] batches [raw_transactions] to the [evm_node] and waits + until the first one is applied in a block and returns, or fails if it isn't + applied after [wait_for_blocks] blocks. *) +val send_n_transactions : + sc_rollup_node:Sc_rollup_node.t -> + client:Client.t -> + evm_node:Evm_node.t -> + ?wait_for_blocks:int -> + string list -> + (Evm_node.request list * Transaction.transaction_receipt * string list) Lwt.t diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index 5e98ed850a15..0299efd31148 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -186,78 +186,6 @@ let check_storage_size sc_rollup_node ~address size = ~error_msg:"Unexpected storage size, should be %R, but is %L" ; unit -(** [wait_for_transaction_receipt ~evm_node ~transaction_hash] takes an - transaction_hash and returns only when the receipt is non null, or [count] - blocks have passed and the receipt is still not available. *) -let wait_for_transaction_receipt ?(count = 3) ~evm_node ~transaction_hash () = - let rec loop count = - let* () = Lwt_unix.sleep 5. in - let* receipt = - Evm_node.( - call_evm_rpc - evm_node - { - method_ = "eth_getTransactionReceipt"; - parameters = `A [`String transaction_hash]; - }) - in - if receipt |> Evm_node.extract_result |> JSON.is_null then - if count > 0 then loop (count - 1) - else Test.fail "Transaction still hasn't been included" - else - receipt |> Evm_node.extract_result - |> Transaction.transaction_receipt_of_json |> return - in - loop count - -let wait_for_application ~evm_node ~sc_rollup_node ~client apply = - let max_iteration = 10 in - let application_result = apply () in - let rec loop current_iteration = - let* () = Lwt_unix.sleep 5. in - let* () = Helpers.next_evm_level ~evm_node ~sc_rollup_node ~client in - if max_iteration < current_iteration then - Test.fail - "Baked more than %d blocks and the operation's application is still \ - pending" - max_iteration ; - if Lwt.state application_result = Lwt.Sleep then loop (current_iteration + 1) - else unit - in - (* Using [Lwt.both] ensures that any exception thrown in [tx_hash] will be - thrown by [Lwt.both] as well. *) - let* result, () = Lwt.both application_result (loop 0) in - return result - -(* sending more than ~300 tx could fail, because or curl *) -let send_n_transactions ~sc_rollup_node ~client ~evm_node ?wait_for_blocks txs = - let requests = - List.map - (fun tx -> - Evm_node. - {method_ = "eth_sendRawTransaction"; parameters = `A [`String tx]}) - txs - in - let* hashes = Evm_node.batch_evm_rpc evm_node requests in - let hashes = - hashes |> JSON.as_list - |> List.map (fun json -> Evm_node.extract_result json |> JSON.as_string) - in - let first_hash = List.hd hashes in - (* Let's wait until one of the transactions is injected into a block, and - test this block contains the `n` transactions as expected. *) - let* receipt = - wait_for_application - ~evm_node - ~sc_rollup_node - ~client - (wait_for_transaction_receipt - ?count:wait_for_blocks - ~evm_node - ~transaction_hash:first_hash) - in - return (requests, receipt, hashes) - let setup_l1_contracts ~admin ?sequencer_admin client = (* Originates the exchanger. *) let* exchanger = @@ -596,7 +524,7 @@ let deploy ~contract ~sender full_evm_setup = ~abi:contract.label ~bin:contract.bin in - wait_for_application ~evm_node ~sc_rollup_node ~client send_deploy + Helpers.wait_for_application ~evm_node ~sc_rollup_node ~client send_deploy type deploy_checks = { contract : contract; -- GitLab From f817ffa7d9aa39e3fe44a14fab85465004e14310 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 26 Mar 2024 19:13:59 +0100 Subject: [PATCH 7/7] EVM/Tezt: test a blueprint will is limited in size. --- etherlink/tezt/tests/evm_sequencer.ml | 192 +++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 6 deletions(-) diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index ecca00053096..c885fafd8336 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -235,8 +235,9 @@ let setup_sequencer ?(devmode = true) ?config ?genesis_timestamp sc_rollup_node; } -let send_raw_transaction_to_delayed_inbox ?(amount = Tez.one) ?expect_failure - ~sc_rollup_node ~client ~l1_contracts ~sc_rollup_address raw_tx = +let send_raw_transaction_to_delayed_inbox ?(wait_for_next_level = true) + ?(amount = Tez.one) ?expect_failure ~sc_rollup_node ~client ~l1_contracts + ~sc_rollup_address ?(sender = Constant.bootstrap2) raw_tx = let expected_hash = `Hex raw_tx |> Hex.to_bytes |> Tezos_crypto.Hacl.Hash.Keccak_256.digest |> Hex.of_bytes |> Hex.show @@ -245,14 +246,18 @@ let send_raw_transaction_to_delayed_inbox ?(amount = Tez.one) ?expect_failure Client.transfer ~arg:(sf "Pair %S 0x%s" sc_rollup_address raw_tx) ~amount - ~giver:Constant.bootstrap2.public_key_hash + ~giver:sender.public_key_hash ~receiver:l1_contracts.delayed_transaction_bridge ~burn_cap:Tez.one ?expect_failure client in - let* () = Client.bake_for_and_wait ~keys:[] client in - let* _ = next_rollup_node_level ~sc_rollup_node ~client in + let* () = + if wait_for_next_level then + let* _ = next_rollup_node_level ~sc_rollup_node ~client in + unit + else unit + in Lwt.return expected_hash let send_deposit_to_delayed_inbox ~amount ~l1_contracts ~depositor ~receiver @@ -2316,6 +2321,179 @@ let test_stage_one_reboot = was expected." ; unit +let test_blueprint_is_limited_in_size = + Protocol.register_test + ~__FILE__ + ~tags:["evm"; "sequencer"; "blueprint"; "limit"] + ~title: + "Checks the sequencer doesn't produce blueprint bigger than the given \ + maximum number of chunks" + ~uses + @@ fun protocol -> + let* {sc_rollup_node; client; sequencer; _} = + setup_sequencer + ~config:(`Path (kernel_inputs_path ^ "/100-inputs-for-proxy-config.yaml")) + ~sequencer:Constant.bootstrap1 + ~time_between_blocks:Nothing + ~max_number_of_chunks:2 + ~minimum_base_fee_per_gas:base_fee_for_hardcoded_tx + protocol + in + let txs = read_tx_from_file () |> List.map (fun (tx, _hash) -> tx) in + let* requests, hashes = + Helpers.batch_n_transactions ~evm_node:sequencer txs + in + (* Each transaction is about 114 bytes, hence 100 * 114 = 11400 bytes, which + will fit in two blueprints of two chunks each. *) + let* () = next_evm_level ~evm_node:sequencer ~sc_rollup_node ~client in + let* () = next_evm_level ~evm_node:sequencer ~sc_rollup_node ~client in + let first_hash = List.hd hashes in + let* level_of_first_transaction = + let*@ receipt = Rpc.get_transaction_receipt ~tx_hash:first_hash sequencer in + match receipt with + | None -> Test.fail "Delayed transaction hasn't be included" + | Some receipt -> return receipt.blockNumber + in + let*@ block_with_first_transaction = + Rpc.get_block_by_number + ~block:(Int32.to_string level_of_first_transaction) + sequencer + in + (* The block containing the first transaction of the batch cannot contain the + 100 transactions of the batch, as it doesn't fit in two chunks. *) + let block_size_of_first_transaction = + match block_with_first_transaction.Block.transactions with + | Block.Empty -> Test.fail "Expected a non empty block" + | Block.Full _ -> + Test.fail "Block is supposed to contain only transaction hashes" + | Block.Hash hashes -> + Check.((List.length hashes < List.length requests) int) + ~error_msg:"Expected less than %R transactions in the block, got %L" ; + List.length hashes + in + + let* () = next_evm_level ~evm_node:sequencer ~sc_rollup_node ~client in + (* It's not clear the first transaction of the batch is applied in the first + blueprint or the second, as it depends how the tx_pool sorts the + transactions (by caller address). We need to check that either the previous + block or the next block contains transactions, which puts in evidence that + the batch has been splitted into two consecutive blueprints. + *) + let check_block_size block_number = + let*@ block = + Rpc.get_block_by_number ~block:(Int32.to_string block_number) sequencer + in + match block.Block.transactions with + | Block.Empty -> return 0 + | Block.Full _ -> + Test.fail "Block is supposed to contain only transaction hashes" + | Block.Hash hashes -> return (List.length hashes) + in + let* next_block_size = + check_block_size (Int32.succ level_of_first_transaction) + in + let* previous_block_size = + check_block_size (Int32.pred level_of_first_transaction) + in + if next_block_size = 0 && previous_block_size = 0 then + Test.fail + "The sequencer didn't apply the 100 transactions in two consecutive \ + blueprints" ; + Check.( + (block_size_of_first_transaction + previous_block_size + next_block_size + = List.length hashes) + int + ~error_msg: + "Not all the transactions have been injected, only %L, while %R was \ + expected.") ; + unit + +let test_blueprint_limit_with_delayed_inbox = + Protocol.register_test + ~__FILE__ + ~tags:["evm"; "sequencer"; "blueprint"; "limit"; "delayed"] + ~title: + "Checks the sequencer doesn't produce blueprint bigger than the given \ + maximum number of chunks and count delayed transactions size in the \ + blueprint" + ~uses + @@ fun protocol -> + let* {sc_rollup_node; client; sequencer; sc_rollup_address; l1_contracts; _} = + setup_sequencer + ~config:(`Path (kernel_inputs_path ^ "/100-inputs-for-proxy-config.yaml")) + ~sequencer:Constant.bootstrap1 + ~time_between_blocks:Nothing + ~max_number_of_chunks:2 + ~minimum_base_fee_per_gas:base_fee_for_hardcoded_tx + ~devmode:true + protocol + in + let txs = read_tx_from_file () |> List.map (fun (tx, _hash) -> tx) in + (* The first 3 transactions will be sent to the delayed inbox *) + let delayed_txs, direct_txs = Tezos_base.TzPervasives.TzList.split_n 3 txs in + let send_to_delayed_inbox (sender, raw_tx) = + send_raw_transaction_to_delayed_inbox + ~wait_for_next_level:false + ~sender + ~sc_rollup_node + ~sc_rollup_address + ~client + ~l1_contracts + raw_tx + in + let* delayed_hashes = + Lwt_list.map_s send_to_delayed_inbox + @@ List.combine + [Constant.bootstrap2; Constant.bootstrap3; Constant.bootstrap4] + delayed_txs + in + (* Ensures the transactions are added to the rollup delayed inbox and picked + by the sequencer *) + let* () = + repeat 4 (fun () -> + let* _l1_level = next_rollup_node_level ~sc_rollup_node ~client in + unit) + in + let* _requests, _hashes = + Helpers.batch_n_transactions ~evm_node:sequencer direct_txs + in + (* Due to the overapproximation of 4096 bytes per delayed transactions, there + should be only a single delayed transaction per blueprints with 2 chunks. *) + let* _ = next_evm_level ~evm_node:sequencer ~sc_rollup_node ~client in + let* _ = next_evm_level ~evm_node:sequencer ~sc_rollup_node ~client in + let* _ = next_evm_level ~evm_node:sequencer ~sc_rollup_node ~client in + (* Checks the delayed transactions and at least the first transaction from the + batch have been applied *) + let* block_numbers = + Lwt_list.map_s + (fun tx_hash -> + let*@ receipt = Rpc.get_transaction_receipt ~tx_hash sequencer in + match receipt with + | None -> Test.fail "Delayed transaction hasn't be included" + | Some receipt -> return receipt.blockNumber) + delayed_hashes + in + let check_block_contains_delayed_transaction_and_transactions + (delayed_hash, block_number) = + let*@ block = + Rpc.get_block_by_number ~block:(Int32.to_string block_number) sequencer + in + match block.Block.transactions with + | Block.Empty -> Test.fail "Block shouldn't be empty" + | Block.Full _ -> + Test.fail "Block is supposed to contain only transaction hashes" + | Block.Hash hashes -> + if not (List.mem ("0x" ^ delayed_hash) hashes && 2 < List.length hashes) + then + Test.fail + "The delayed transaction %s hasn't been included in the expected \ + block along other transactions from the pool" + delayed_hash ; + unit + in + Lwt_list.iter_s check_block_contains_delayed_transaction_and_transactions + @@ List.combine delayed_hashes block_numbers + let protocols = Protocol.all let () = @@ -2349,4 +2527,6 @@ let () = test_sequencer_upgrade protocols ; test_sequencer_diverge protocols ; test_sequencer_can_catch_up_on_event protocols ; - test_stage_one_reboot protocols + test_stage_one_reboot protocols ; + test_blueprint_is_limited_in_size protocols ; + test_blueprint_limit_with_delayed_inbox protocols -- GitLab