From 7e0e68ad5b674d49d70e848bd8cac9bdbec8681c Mon Sep 17 00:00:00 2001 From: Thomas Letan Date: Mon, 7 Jul 2025 14:27:11 +0200 Subject: [PATCH] EVM Node: Halt block production before sequencer upgrade * What This patch introduces a mechanism to temporarily halt block production by the Etherlink sequencer five minutes prior to a planned sequencer operator upgrade. This ensures a controlled shutdown of block production activities. * Why Halting block production before a sequencer upgrade is crucial for ensuring a smooth and safe transition. It minimizes the risk of issues that could arise from producing blocks during the upgrade process, such as missed blocks, inconsistent state, or transaction failures, thereby contributing to the overall stability and reliability of the Etherlink network. * How The block producer now checks for a pending sequencer upgrade event. If an upgrade is scheduled, and the current timestamp is within five minutes of the upgrade's timestamp, the block production process is skipped for that specific block. This check is distinct from the existing kernel upgrade check. --- etherlink/CHANGES_NODE.md | 5 + etherlink/bin_node/config/configuration.ml | 44 +++++- etherlink/bin_node/config/configuration.mli | 4 + etherlink/bin_node/lib_dev/block_producer.ml | 144 ++++++++++-------- etherlink/bin_node/lib_dev/block_producer.mli | 1 + .../bin_node/lib_dev/block_producer_events.ml | 10 ++ etherlink/bin_node/lib_dev/sequencer.ml | 1 + etherlink/bin_node/main.ml | 29 +++- etherlink/tezt/lib/evm_node.ml | 5 + etherlink/tezt/lib/evm_node.mli | 1 + etherlink/tezt/lib/setup.ml | 22 ++- etherlink/tezt/lib/setup.mli | 5 + etherlink/tezt/tests/evm_rollup.ml | 3 + etherlink/tezt/tests/evm_sequencer.ml | 67 ++++++++ .../EVM node- list events regression.out | 8 + .../Alpha- Configuration RPC.out | 9 +- .../EVM Node- describe config.out | 6 +- .../evm_sequencer.ml/EVM Node- man.out | 9 +- .../upgrade_etherlink.ml | 1 + tezt/tests/cloud/dal.ml | 1 + 20 files changed, 285 insertions(+), 90 deletions(-) diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index eaf752727cc1..493b8426dd25 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -7,6 +7,9 @@ ### Configuration changes - Fix encodings for sequencer key when using a remote key on GCP KMS. (!18642) +- Add a new configuration option for the sequencer to stop block production + ahead of a sequencer upgrade. See `describe config` or `man run sequencer`. + (!18605) ### RPCs changes @@ -18,6 +21,8 @@ - Add a new command `show gcp key` which can be used to retrieve Tezos-specific (b58-encoded public key, Tezos implicit account) and Ethereum-specific (EOA address) information about a key stored in a GCP KMS. (!18617 !18618) +- Halt blocks production five minutes before a planned upgrade of the sequencer + operator. (!18605) ### Storage changes diff --git a/etherlink/bin_node/config/configuration.ml b/etherlink/bin_node/config/configuration.ml index 5b912addb47e..26c767548040 100644 --- a/etherlink/bin_node/config/configuration.ml +++ b/etherlink/bin_node/config/configuration.ml @@ -13,8 +13,13 @@ let pp_supported_network fmt network = fmt (match network with Mainnet -> "mainnet" | Testnet -> "testnet") +let positive_encoding = Data_encoding.ranged_int 0 ((1 lsl 30) - 1) + let strictly_positive_encoding = Data_encoding.ranged_int 1 ((1 lsl 30) - 1) +let positive_i64_encoding = + Data_encoding.(conv Int64.to_int Int64.of_int positive_encoding) + type log_filter_config = { max_nb_blocks : int; max_nb_logs : int; @@ -194,6 +199,7 @@ type sequencer = { max_number_of_chunks : int; sequencer : sequencer_key option; blueprints_publisher_config : blueprints_publisher_config; + sunset_sec : int64; } type gcp_authentication_method = Gcloud_auth | Metadata_server @@ -519,9 +525,11 @@ let kernel_execution_config_dft ~data_dir ?preimages ?preimages_endpoint native_execution_policy; } +let default_sequencer_sunset_sec = 300L + let sequencer_config_dft ?time_between_blocks ?max_number_of_chunks ?sequencer ?max_blueprints_lag ?max_blueprints_ahead ?max_blueprints_catchup - ?catchup_cooldown ?dal_slots () = + ?catchup_cooldown ?dal_slots ?sunset_sec () = let default_blueprints_publisher_config = if Option.is_some dal_slots then default_blueprints_publisher_config_with_dal @@ -555,6 +563,7 @@ let sequencer_config_dft ?time_between_blocks ?max_number_of_chunks ?sequencer Option.value ~default:default_max_number_of_chunks max_number_of_chunks; sequencer; blueprints_publisher_config; + sunset_sec = Option.value ~default:default_sequencer_sunset_sec sunset_sec; } let observer_evm_node_endpoint = function @@ -774,22 +783,26 @@ let sequencer_encoding = max_number_of_chunks; sequencer; blueprints_publisher_config; + sunset_sec; } -> ( time_between_blocks, max_number_of_chunks, sequencer, - blueprints_publisher_config )) + blueprints_publisher_config, + sunset_sec )) (fun ( time_between_blocks, max_number_of_chunks, sequencer, - blueprints_publisher_config ) -> + blueprints_publisher_config, + sunset_sec ) -> { time_between_blocks; max_number_of_chunks; sequencer; blueprints_publisher_config; + sunset_sec; }) - (obj4 + (obj5 time_between_blocks_field max_number_of_chunks_field (opt @@ -799,7 +812,14 @@ let sequencer_encoding = (dft "blueprints_publisher_config" blueprints_publisher_config_encoding - default_blueprints_publisher_config)) + default_blueprints_publisher_config) + (dft + ~description: + "Number of seconds prior to a sequencer operator upgrade before \ + which the current sequencer stops producing blocks" + "sunset_sec" + positive_i64_encoding + default_sequencer_sunset_sec)) let observer_encoding ?network () = let open Data_encoding in @@ -1934,7 +1954,7 @@ module Cli = struct ?log_filter_max_nb_logs ?log_filter_chunk_size ?max_blueprints_lag ?max_blueprints_ahead ?max_blueprints_catchup ?catchup_cooldown ?restricted_rpcs ?finalized_view ?proxy_ignore_block_param ?history_mode - ?dal_slots configuration = + ?dal_slots ?sunset_sec configuration = let public_rpc = patch_rpc ?rpc_addr @@ -1994,6 +2014,9 @@ module Cli = struct Option.either dal_slots blueprints_publisher_config.dal_slots; } in + let sunset_sec = + Option.value ~default:sequencer_config.sunset_sec sunset_sec + in { time_between_blocks = Option.value @@ -2005,6 +2028,7 @@ module Cli = struct max_number_of_chunks; sequencer = Option.either sequencer_key sequencer_config.sequencer; blueprints_publisher_config; + sunset_sec; } in let observer = @@ -2121,7 +2145,8 @@ module Cli = struct ?log_filter_max_nb_blocks ?log_filter_max_nb_logs ?log_filter_chunk_size ?max_blueprints_lag ?max_blueprints_ahead ?max_blueprints_catchup ?catchup_cooldown ?restricted_rpcs ?finalized_view - ?proxy_ignore_block_param ?dal_slots ?network ?history_mode () = + ?proxy_ignore_block_param ?dal_slots ?network ?history_mode ?sunset_sec () + = default ~data_dir ?network ?evm_node_endpoint () |> patch_configuration_from_args ?rpc_addr @@ -2158,6 +2183,7 @@ module Cli = struct ?proxy_ignore_block_param ?dal_slots ?history_mode + ?sunset_sec let create_or_read_config ~data_dir ?rpc_addr ?rpc_port ?rpc_batch_limit ?cors_origins ?cors_headers ?enable_websocket ?tx_pool_timeout_limit @@ -2169,7 +2195,7 @@ module Cli = struct ?max_blueprints_ahead ?max_blueprints_catchup ?catchup_cooldown ?log_filter_max_nb_blocks ?log_filter_max_nb_logs ?log_filter_chunk_size ?restricted_rpcs ?finalized_view ?proxy_ignore_block_param ?dal_slots - ?network ?history_mode config_file = + ?network ?history_mode ?sunset_sec config_file = let open Lwt_result_syntax in let open Filename.Infix in (* Check if the data directory of the evm node is not the one of Octez @@ -2225,6 +2251,7 @@ module Cli = struct ?proxy_ignore_block_param ?history_mode ?dal_slots + ?sunset_sec configuration in return configuration @@ -2267,6 +2294,7 @@ module Cli = struct ?dal_slots ?network ?history_mode + ?sunset_sec () in return config diff --git a/etherlink/bin_node/config/configuration.mli b/etherlink/bin_node/config/configuration.mli index 3e199196cb0f..20a53c47b597 100644 --- a/etherlink/bin_node/config/configuration.mli +++ b/etherlink/bin_node/config/configuration.mli @@ -147,6 +147,7 @@ type sequencer = { (** The maximum number of chunks per blueprints. *) sequencer : sequencer_key option; (** The key used to sign the blueprints. *) blueprints_publisher_config : blueprints_publisher_config; + sunset_sec : int64; } type observer = {evm_node_endpoint : Uri.t; rollup_node_tracking : bool} @@ -332,6 +333,7 @@ module Cli : sig ?dal_slots:int list -> ?network:supported_network -> ?history_mode:history_mode -> + ?sunset_sec:int64 -> unit -> t @@ -370,6 +372,7 @@ module Cli : sig ?proxy_ignore_block_param:bool -> ?history_mode:history_mode -> ?dal_slots:int list -> + ?sunset_sec:int64 -> t -> t @@ -410,6 +413,7 @@ module Cli : sig ?dal_slots:int list -> ?network:supported_network -> ?history_mode:history_mode -> + ?sunset_sec:int64 -> string -> t tzresult Lwt.t end diff --git a/etherlink/bin_node/lib_dev/block_producer.ml b/etherlink/bin_node/lib_dev/block_producer.ml index 814a0d4d6fd9..067ec9c169f6 100644 --- a/etherlink/bin_node/lib_dev/block_producer.ml +++ b/etherlink/bin_node/lib_dev/block_producer.ml @@ -10,6 +10,7 @@ type parameters = { smart_rollup_address : string; maximum_number_of_chunks : int; tx_container : Services_backend_sig.ex_tx_container; + sequencer_sunset_sec : int64; } (* The size of a delayed transaction is overapproximated to the maximum size @@ -60,7 +61,14 @@ let minimum_ethereum_transaction_size = module Types = struct type nonrec parameters = parameters - type state = parameters + type state = { + signer : Signer.t; + smart_rollup_address : string; + maximum_number_of_chunks : int; + tx_container : Services_backend_sig.ex_tx_container; + sequencer_sunset_sec : int64; + mutable sunset : bool; + } end module Name = struct @@ -364,55 +372,69 @@ let head_info_and_delayed_transactions ~with_delayed_transactions Sequencer_blueprint.maximum_usable_space_in_blueprint maximum_number_of_chunks ) in - let*! head_info = Evm_context.head_info () in - return (head_info, delayed_hashes, remaining_cumulative_size) + return (delayed_hashes, remaining_cumulative_size) -let produce_block (type f) ~signer ~smart_rollup_address ~force ~timestamp - ~maximum_number_of_chunks ~with_delayed_transactions - ~(tx_container : f Services_backend_sig.tx_container) = +let produce_block (type f) (state : Types.state) ~force ~timestamp + ~with_delayed_transactions = let open Lwt_result_syntax in - let (module Tx_container) = - Services_backend_sig.tx_container_module tx_container - in - let* is_locked = Tx_container.is_locked () in - if is_locked then - let*! () = Block_producer_events.production_locked () in - return `No_block - else - let* head_info, delayed_hashes, remaining_cumulative_size = - head_info_and_delayed_transactions - ~with_delayed_transactions - maximum_number_of_chunks - in - let is_going_to_upgrade = - match head_info.pending_upgrade with - | Some Evm_events.Upgrade.{hash = _; timestamp = upgrade_timestamp} -> - timestamp >= upgrade_timestamp - | None -> false - in - if is_going_to_upgrade then - let* hashes = - produce_block_with_transactions - ~signer - ~timestamp - ~smart_rollup_address - ~transactions_and_objects:None - ~delayed_hashes:[] - ~tx_container - head_info + match state.tx_container with + | Ex_tx_container tx_container -> + let (module Tx_container) = + Services_backend_sig.tx_container_module tx_container in - (* (Seq.length hashes) is always zero, this is only to be "future" proof *) - return (`Block_produced (Seq.length hashes)) - else - produce_block_if_needed - ~signer - ~timestamp - ~smart_rollup_address - ~force - ~delayed_hashes - ~remaining_cumulative_size - ~tx_container - head_info + let*! head_info = Evm_context.head_info () in + let* () = + when_ (not state.sunset) @@ fun () -> + match head_info.pending_sequencer_upgrade with + | Some Evm_events.Sequencer_upgrade.{timestamp = upgrade_timestamp; _} + when Time.Protocol.( + add timestamp state.sequencer_sunset_sec >= upgrade_timestamp + && timestamp < upgrade_timestamp) -> + (* We stop producing blocks ahead of the upgrade *) + let*! () = Block_producer_events.sunset () in + state.sunset <- true ; + Tx_container.lock_transactions () + | _ -> return_unit + in + let* is_locked = Tx_container.is_locked () in + if is_locked then + let*! () = Block_producer_events.production_locked () in + return `No_block + else + let* delayed_hashes, remaining_cumulative_size = + head_info_and_delayed_transactions + ~with_delayed_transactions + state.maximum_number_of_chunks + in + let is_going_to_upgrade_kernel = + match head_info.pending_upgrade with + | Some Evm_events.Upgrade.{hash = _; timestamp = upgrade_timestamp} -> + timestamp >= upgrade_timestamp + | None -> false + in + if is_going_to_upgrade_kernel then + let* hashes = + produce_block_with_transactions + ~signer:state.signer + ~timestamp + ~smart_rollup_address:state.smart_rollup_address + ~transactions_and_objects:None + ~delayed_hashes:[] + ~tx_container + head_info + in + (* (Seq.length hashes) is always zero, this is only to be "future" proof *) + return (`Block_produced (Seq.length hashes)) + else + produce_block_if_needed + ~signer:state.signer + ~timestamp + ~smart_rollup_address:state.smart_rollup_address + ~force + ~delayed_hashes + ~remaining_cumulative_size + ~tx_container + head_info module Handlers = struct type self = worker @@ -426,27 +448,21 @@ module Handlers = struct match request with | Request.Produce_block (with_delayed_transactions, timestamp, force) -> protect @@ fun () -> - let { - signer; - smart_rollup_address; - maximum_number_of_chunks; - tx_container = Ex_tx_container tx_container; - } = - state - in - produce_block - ~signer - ~smart_rollup_address - ~force - ~timestamp - ~maximum_number_of_chunks - ~with_delayed_transactions - ~tx_container + produce_block state ~force ~timestamp ~with_delayed_transactions type launch_error = error trace let on_launch _w () (parameters : Types.parameters) = - Lwt_result_syntax.return parameters + Lwt_result_syntax.return + Types. + { + sunset = false; + signer = parameters.signer; + smart_rollup_address = parameters.smart_rollup_address; + maximum_number_of_chunks = parameters.maximum_number_of_chunks; + tx_container = parameters.tx_container; + sequencer_sunset_sec = parameters.sequencer_sunset_sec; + } let on_error (type a b) _w _st (_r : (a, b) Request.t) (_errs : b) : [`Continue | `Shutdown] tzresult Lwt.t = diff --git a/etherlink/bin_node/lib_dev/block_producer.mli b/etherlink/bin_node/lib_dev/block_producer.mli index e8ac69f62150..e6a81acc88d5 100644 --- a/etherlink/bin_node/lib_dev/block_producer.mli +++ b/etherlink/bin_node/lib_dev/block_producer.mli @@ -10,6 +10,7 @@ type parameters = { smart_rollup_address : string; maximum_number_of_chunks : int; tx_container : Services_backend_sig.ex_tx_container; + sequencer_sunset_sec : int64; } (** [start parameters] starts the events follower. *) diff --git a/etherlink/bin_node/lib_dev/block_producer_events.ml b/etherlink/bin_node/lib_dev/block_producer_events.ml index 2cb27a80a61f..923b803e8277 100644 --- a/etherlink/bin_node/lib_dev/block_producer_events.ml +++ b/etherlink/bin_node/lib_dev/block_producer_events.ml @@ -53,6 +53,14 @@ module Event = struct Format.fprintf fmt "%10s" h) ("tx_hash", Ethereum_types.hash_encoding) ("error", Data_encoding.string) + + let sunset = + declare_0 + ~section + ~name:"block_production_sunset" + ~msg:"block production is sunset ahead of the change of sequencer" + ~level:Notice + () end let transaction_selected ~hash = @@ -66,3 +74,5 @@ let production_locked () = Internal_event.Simple.emit Event.production_locked () let transaction_rejected tx_hash error = Internal_event.Simple.emit Event.transaction_rejected (tx_hash, error) + +let sunset () = Internal_event.Simple.emit Event.sunset () diff --git a/etherlink/bin_node/lib_dev/sequencer.ml b/etherlink/bin_node/lib_dev/sequencer.ml index 394530ca0e0c..fd1fb58a59f6 100644 --- a/etherlink/bin_node/lib_dev/sequencer.ml +++ b/etherlink/bin_node/lib_dev/sequencer.ml @@ -405,6 +405,7 @@ let main ~data_dir ~cctxt ?signer ?(genesis_timestamp = Misc.now ()) smart_rollup_address = smart_rollup_address_b58; maximum_number_of_chunks = sequencer_config.max_number_of_chunks; tx_container = Ex_tx_container tx_container; + sequencer_sunset_sec = sequencer_config.sunset_sec; } in let* () = diff --git a/etherlink/bin_node/main.ml b/etherlink/bin_node/main.ml index ee3a177c8a61..d89ccadfa2de 100644 --- a/etherlink/bin_node/main.ml +++ b/etherlink/bin_node/main.ml @@ -324,6 +324,16 @@ let catchup_cooldown_arg = resending its blueprints before trying to catch-up again." Params.int +let sunset_sec_arg = + Tezos_clic.arg + ~env:(config_env "SEQUENCER_SUNSET_SEC") + ~long:"sunset-sec" + ~placeholder:"SEC" + ~doc: + "Number of seconds prior to a sequencer operator upgrade before which \ + the current sequencer stops producing blocks" + Params.int64 + let cors_allowed_headers_arg = Tezos_clic.arg ~long:"cors-headers" @@ -1090,7 +1100,7 @@ let start_sequencer ~wallet_ctxt ~data_dir ?sequencer_key ?rpc_addr ?rpc_port ?max_blueprints_ahead ?max_blueprints_catchup ?catchup_cooldown ?log_filter_max_nb_blocks ?log_filter_max_nb_logs ?log_filter_chunk_size ?genesis_timestamp ?restricted_rpcs ?kernel ?dal_slots ?sandbox_config - ~finalized_view config_file = + ~finalized_view ?sunset_sec config_file = let open Lwt_result_syntax in let* configuration = Cli.create_or_read_config @@ -1125,6 +1135,7 @@ let start_sequencer ~wallet_ctxt ~data_dir ?sequencer_key ?rpc_addr ?rpc_port ?restricted_rpcs ?dal_slots ~finalized_view + ?sunset_sec config_file in let*! () = init_logs ~daily_logs:true ~data_dir configuration in @@ -1872,7 +1883,7 @@ let init_config_command = then adds the configuration for the observer mode." (merge_options common_config_args - (args18 + (args19 (* sequencer and observer config*) preimages_arg preimages_endpoint_arg @@ -1900,7 +1911,8 @@ let init_config_command = ~why: "If set, some configuration options defaults to well-known \ values for the selected network." - ()))) + ()) + sunset_sec_arg)) (prefixes ["init"; "config"] @@ stop) (fun ( ( data_dir, config_file, @@ -1941,7 +1953,8 @@ let init_config_command = wallet_dir, force, dal_slots, - network ) ) + network, + sequencer_sunset_sec ) ) () -> let config_file = config_filename ~data_dir config_file in Evm_node_lib_dev.Data_dir.use ~data_dir @@ fun () -> @@ -1995,6 +2008,7 @@ let init_config_command = ~finalized_view ?network ?history_mode + ?sunset_sec:sequencer_sunset_sec config_file in let*! () = init_logs ~daily_logs:false ~data_dir config in @@ -2403,7 +2417,7 @@ let proxy_command = ()) let sequencer_config_args = - Tezos_clic.args15 + Tezos_clic.args16 preimages_arg preimages_endpoint_arg time_between_blocks_arg @@ -2419,6 +2433,7 @@ let sequencer_config_args = wallet_dir_arg (Client_config.password_filename_arg ()) dal_slots_arg + sunset_sec_arg let fund_arg = let long = "fund" in @@ -2494,7 +2509,8 @@ let sequencer_command = kernel, wallet_dir, password_filename, - dal_slots ) ) + dal_slots, + sunset_sec ) ) () -> let open Lwt_result_syntax in let* restricted_rpcs = @@ -2541,6 +2557,7 @@ let sequencer_command = ?kernel ?dal_slots ~finalized_view + ?sunset_sec config_file) let sandbox_command = diff --git a/etherlink/tezt/lib/evm_node.ml b/etherlink/tezt/lib/evm_node.ml index 996d6c62db9c..b01cd705be74 100644 --- a/etherlink/tezt/lib/evm_node.ml +++ b/etherlink/tezt/lib/evm_node.ml @@ -90,6 +90,7 @@ type mode = tx_pool_addr_limit : int option; tx_pool_tx_per_addr_limit : int option; dal_slots : int list option; + sequencer_sunset_sec : int option; } | Sandbox of { initial_kernel : string; @@ -729,6 +730,7 @@ let mode_with_new_private_rpc (mode : mode) = tx_pool_addr_limit; tx_pool_tx_per_addr_limit; dal_slots; + sequencer_sunset_sec; } -> Sequencer { @@ -748,6 +750,7 @@ let mode_with_new_private_rpc (mode : mode) = tx_pool_addr_limit; tx_pool_tx_per_addr_limit; dal_slots; + sequencer_sunset_sec; } | Sandbox { @@ -1045,6 +1048,7 @@ let spawn_init_config ?(extra_arguments = []) evm_node = tx_pool_addr_limit; tx_pool_tx_per_addr_limit; dal_slots; + sequencer_sunset_sec; } -> [ "--rollup-node-endpoint"; @@ -1095,6 +1099,7 @@ let spawn_init_config ?(extra_arguments = []) evm_node = "dal-slots" (fun l -> String.concat "," (List.map string_of_int l)) dal_slots + @ Cli_arg.optional_arg "sunset-sec" string_of_int sequencer_sunset_sec | Sandbox { initial_kernel = _; diff --git a/etherlink/tezt/lib/evm_node.mli b/etherlink/tezt/lib/evm_node.mli index 843e7ec73b72..2a7ba4e20f33 100644 --- a/etherlink/tezt/lib/evm_node.mli +++ b/etherlink/tezt/lib/evm_node.mli @@ -90,6 +90,7 @@ type mode = (** --tx-pool-tx-per-addr-limit: maximum transaction per address allowed simultaneously inside the pool. *) dal_slots : int list option; + sequencer_sunset_sec : int option; } | Sandbox of { initial_kernel : string; diff --git a/etherlink/tezt/lib/setup.ml b/etherlink/tezt/lib/setup.ml index 63d208139e2c..bd941cdf6310 100644 --- a/etherlink/tezt/lib/setup.ml +++ b/etherlink/tezt/lib/setup.ml @@ -480,7 +480,7 @@ let setup_sequencer_internal ?max_delayed_inbox_blueprint_length ?(blueprints_publisher_order_enabled = true) ?rollup_history_mode ~enable_dal ?dal_slots ~enable_multichain ~l2_chains ?rpc_server ?websockets ?history_mode ?enable_tx_queue ?spawn_rpc ?periodic_snapshot_path - ?(signatory = false) protocol = + ?(signatory = false) ?(sequencer_sunset_sec = 0) protocol = let* node, client = setup_l1 ?commitment_period @@ -653,6 +653,7 @@ let setup_sequencer_internal ?max_delayed_inbox_blueprint_length tx_pool_addr_limit = None; tx_pool_tx_per_addr_limit = None; dal_slots; + sequencer_sunset_sec = Some sequencer_sunset_sec; } in let* sequencer = @@ -727,7 +728,8 @@ let setup_sequencer ?max_delayed_inbox_blueprint_length ?next_wasm_runtime ?drop_duplicate_when_injection ?blueprints_publisher_order_enabled ?rollup_history_mode ~enable_dal ?dal_slots ~enable_multichain ?rpc_server ?websockets ?history_mode ?enable_tx_queue ?spawn_rpc - ?periodic_snapshot_path ?signatory ?l2_chains protocol = + ?periodic_snapshot_path ?signatory ?l2_chains ?sequencer_sunset_sec protocol + = (* Note that the chain_id is not important (it will become important later) *) let l2_chains = Option.value @@ -786,6 +788,7 @@ let setup_sequencer ?max_delayed_inbox_blueprint_length ?next_wasm_runtime ?spawn_rpc ?periodic_snapshot_path ?signatory + ?sequencer_sunset_sec protocol in return (multichain_setup_to_single ~setup:sequencer_setup) @@ -805,8 +808,8 @@ let register_multichain_test ~__FILE__ ?max_delayed_inbox_blueprint_length ?(uses = uses) ?(additional_uses = []) ?rollup_history_mode ~enable_dal ?(dal_slots = if enable_dal then Some [0; 1; 2; 3] else None) ~enable_multichain ~l2_setups ?rpc_server ?websockets ?history_mode - ?enable_tx_queue ?spawn_rpc ?periodic_snapshot_path ?signatory body ~title - ~tags protocols = + ?enable_tx_queue ?spawn_rpc ?periodic_snapshot_path ?signatory + ?sequencer_sunset_sec body ~title ~tags protocols = let kernel_tag, kernel_use = Kernel.to_uses_and_tags kernel in let tags = kernel_tag :: tags in let additional_uses = @@ -876,6 +879,7 @@ let register_multichain_test ~__FILE__ ?max_delayed_inbox_blueprint_length ?spawn_rpc ?periodic_snapshot_path ?signatory + ?sequencer_sunset_sec protocol in body sequencer_setup protocol @@ -929,7 +933,7 @@ let register_test ~__FILE__ ?max_delayed_inbox_blueprint_length ?challenge_window ?uses ?additional_uses ?rollup_history_mode ~enable_dal ?dal_slots ~enable_multichain ?rpc_server ?websockets ?history_mode ?enable_tx_queue ?spawn_rpc ?periodic_snapshot_path ?signatory ?l2_setups - body ~title ~tags protocols = + ?sequencer_sunset_sec body ~title ~tags protocols = let body sequencer_setup = body (multichain_setup_to_single ~setup:sequencer_setup) in @@ -978,6 +982,7 @@ let register_test ~__FILE__ ?max_delayed_inbox_blueprint_length ?periodic_snapshot_path ?signatory ~l2_setups + ?sequencer_sunset_sec body ~title ~tags @@ -997,7 +1002,7 @@ let register_test_for_kernels ~__FILE__ ?max_delayed_inbox_blueprint_length ?challenge_window ?additional_uses ~enable_dal ?dal_slots ~enable_multichain ?rpc_server ?websockets ?enable_fast_withdrawal ?enable_fast_fa_withdrawal ?history_mode ?enable_tx_queue ?spawn_rpc ?periodic_snapshot_path ?signatory - ?l2_setups ~title ~tags body protocols = + ?l2_setups ?sequencer_sunset_sec ~title ~tags body protocols = List.iter (fun kernel -> register_test @@ -1044,6 +1049,7 @@ let register_test_for_kernels ~__FILE__ ?max_delayed_inbox_blueprint_length ?periodic_snapshot_path ?signatory ?l2_setups + ?sequencer_sunset_sec ~title ~tags body @@ -1092,7 +1098,8 @@ let register_all ~__FILE__ ?max_delayed_inbox_blueprint_length ?(use_dal = default_dal_registration) ?(use_multichain = default_multichain_registration) ?(use_revm = default_revm_registration) ?enable_tx_queue ?spawn_rpc - ?periodic_snapshot_path ?signatory ?l2_setups ~title ~tags body protocols = + ?periodic_snapshot_path ?signatory ?l2_setups ?sequencer_sunset_sec ~title + ~tags body protocols = let register_cases = function | Register_both {additional_tags_with; additional_tags_without} -> [(false, additional_tags_without); (true, additional_tags_with)] @@ -1160,6 +1167,7 @@ let register_all ~__FILE__ ?max_delayed_inbox_blueprint_length ?periodic_snapshot_path ?signatory ?l2_setups + ?sequencer_sunset_sec ~title ~tags:(dal_tags @ multichain_tags @ revm_tags @ tags) body diff --git a/etherlink/tezt/lib/setup.mli b/etherlink/tezt/lib/setup.mli index a98b9fd9a6ab..29023d69b2eb 100644 --- a/etherlink/tezt/lib/setup.mli +++ b/etherlink/tezt/lib/setup.mli @@ -123,6 +123,7 @@ val register_test : ?periodic_snapshot_path:string -> ?signatory:bool -> ?l2_setups:Evm_node.l2_setup list -> + ?sequencer_sunset_sec:int -> (sequencer_setup -> Protocol.t -> unit Lwt.t) -> title:string -> tags:string list -> @@ -175,6 +176,7 @@ val register_multichain_test : ?spawn_rpc:int -> ?periodic_snapshot_path:string -> ?signatory:bool -> + ?sequencer_sunset_sec:int -> (multichain_sequencer_setup -> Protocol.t -> unit Lwt.t) -> title:string -> tags:string list -> @@ -228,6 +230,7 @@ val register_test_for_kernels : ?periodic_snapshot_path:string -> ?signatory:bool -> ?l2_setups:Evm_node.l2_setup list -> + ?sequencer_sunset_sec:int -> title:string -> tags:string list -> (sequencer_setup -> Protocol.t -> unit Lwt.t) -> @@ -280,6 +283,7 @@ val setup_sequencer : ?periodic_snapshot_path:string -> ?signatory:bool -> ?l2_chains:Evm_node.l2_setup list -> + ?sequencer_sunset_sec:int -> Protocol.t -> sequencer_setup Lwt.t @@ -345,6 +349,7 @@ val register_all : ?periodic_snapshot_path:string -> ?signatory:bool -> ?l2_setups:Evm_node.l2_setup list -> + ?sequencer_sunset_sec:int -> title:string -> tags:string list -> (sequencer_setup -> Protocol.t -> unit Lwt.t) -> diff --git a/etherlink/tezt/tests/evm_rollup.ml b/etherlink/tezt/tests/evm_rollup.ml index 955dc466a203..e3b3d5daeaca 100644 --- a/etherlink/tezt/tests/evm_rollup.ml +++ b/etherlink/tezt/tests/evm_rollup.ml @@ -481,6 +481,7 @@ let setup_evm_kernel ?additional_config ?(setup_kernel_root_hash = true) tx_pool_addr_limit; tx_pool_tx_per_addr_limit; dal_slots; + sequencer_sunset_sec = None; } in let* sequencer = @@ -5050,6 +5051,7 @@ let test_migrate_proxy_to_sequencer_future = tx_pool_addr_limit = None; tx_pool_tx_per_addr_limit = None; dal_slots = None; + sequencer_sunset_sec = None; } in Evm_node.create ~mode (Sc_rollup_node.endpoint sc_rollup_node) @@ -5220,6 +5222,7 @@ let test_migrate_proxy_to_sequencer_past = tx_pool_addr_limit = None; tx_pool_tx_per_addr_limit = None; dal_slots = None; + sequencer_sunset_sec = None; } in Evm_node.create ~mode (Sc_rollup_node.endpoint sc_rollup_node) diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 75f77e180f0d..505d6e616299 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -345,6 +345,7 @@ let test_make_l2_kernel_installer_config chain_family = tx_pool_addr_limit = None; tx_pool_tx_per_addr_limit = None; dal_slots = None; + sequencer_sunset_sec = None; } in let* sequencer = @@ -520,6 +521,7 @@ let test_observer_reset = tx_pool_addr_limit = None; tx_pool_tx_per_addr_limit = None; dal_slots = None; + sequencer_sunset_sec = None; }) (Sc_rollup_node.endpoint sc_rollup_node) in @@ -563,6 +565,7 @@ let test_observer_reset = tx_pool_addr_limit = None; tx_pool_tx_per_addr_limit = None; dal_slots = None; + sequencer_sunset_sec = None; }) (Sc_rollup_node.endpoint temp_sc_rollup_node) in @@ -5897,6 +5900,69 @@ let test_timestamp_from_the_future = unit +let test_sequencer_sunset = + let sequencer_key = Constant.bootstrap1 in + let new_sequencer_key = Constant.bootstrap2 in + let timestamp dt = Format.sprintf "2020-01-01T00:00:%02dZ" dt in + let activation_delay = 30 in + let genesis_timestamp = timestamp 0 in + let activation_timestamp = timestamp activation_delay in + let sunset = 10 in + + register_all + ~__FILE__ + ~title: + "The sequencer locks its transaction queue ahead of the sequencer upgrade" + ~tags:["evm"; "sequencer"; "sequencer_upgrade"; "sunset"; "lock"] + ~sequencer:sequencer_key + ~time_between_blocks:Nothing + ~sequencer_sunset_sec:sunset + ~use_multichain: + (* TODO #7843: Adapt this test to multichain context *) + Register_without_feature + ~genesis_timestamp:Client.(At (Time.of_notation_exn genesis_timestamp)) + @@ fun {sequencer; sc_rollup_address; l1_contracts; client; sc_rollup_node; _} + _protocol -> + (* Check we can create a block *) + let*@ _txns_count = produce_block ~timestamp:(timestamp 1) sequencer in + + let* () = + sequencer_upgrade + ~sc_rollup_address + ~sequencer_admin:Constant.bootstrap2.alias + ~sequencer_governance_contract:l1_contracts.sequencer_governance + ~pool_address:Eth_account.bootstrap_accounts.(0).address + ~client + ~upgrade_to:new_sequencer_key.alias + ~activation_timestamp + in + + let upgrade_info = Evm_node.wait_for_evm_event Sequencer_upgrade sequencer in + let* () = + repeat 2 (fun () -> + let* _ = next_rollup_node_level ~client ~sc_rollup_node in + unit) + and* _upgrade_info = upgrade_info in + + (* Check we can still create a block before the sunset *) + let*@ _txns_count = + produce_block + ~timestamp:(timestamp (activation_delay - sunset - 1)) + sequencer + in + let*@? _err = + produce_block + ~timestamp:(timestamp (activation_delay - sunset + 1)) + sequencer + in + let*@? _err = + produce_block + ~timestamp:(timestamp (activation_delay - sunset + 2)) + sequencer + in + + unit + (** This tests the situation where the kernel has an upgrade and the sequencer upgrade by following the event of the kernel. Observer must upgrade as well, even the one that is not connected to a @@ -13240,6 +13306,7 @@ let () = test_non_increasing_timestamp protocols ; test_timestamp_from_the_future protocols ; test_sequencer_upgrade protocols ; + test_sequencer_sunset protocols ; test_sequencer_diverge protocols ; test_sequencer_can_catch_up_on_event protocols ; test_sequencer_dont_read_level_twice protocols ; diff --git a/etherlink/tezt/tests/expected/evm_rollup.ml/EVM node- list events regression.out b/etherlink/tezt/tests/expected/evm_rollup.ml/EVM node- list events regression.out index 9c8681808fc6..296b3b268772 100644 --- a/etherlink/tezt/tests/expected/evm_rollup.ml/EVM node- list events regression.out +++ b/etherlink/tezt/tests/expected/evm_rollup.ml/EVM node- list events regression.out @@ -85,6 +85,14 @@ block_producer_transaction_rejected: contain invalid byte sequences. */ string || { "invalid_utf8_string": [ integer ∈ [0, 255] ... ] } +block_production_sunset: + description: block production is sunset ahead of the change of sequencer + level: notice + section: evm_node.dev + json format: + { /* block_production_sunset version 0 */ + "block_production_sunset.v0": any } + blueprint_application: description: head is now {level}, applied in {process_time}{timestamp} level: notice diff --git a/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Configuration RPC.out b/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Configuration RPC.out index 0ca0eb8090b8..332b9e3ec5b5 100644 --- a/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Configuration RPC.out +++ b/etherlink/tezt/tests/expected/evm_sequencer.ml/Alpha- Configuration RPC.out @@ -12,7 +12,8 @@ "max_blueprints_ahead": 100, "max_blueprints_catchup": 50, "catchup_cooldown": 1 - } + }, + "sunset_sec": 300 }, "tx_pool_timeout_limit": "3600", "tx_pool_addr_limit": "4000", @@ -91,7 +92,8 @@ "max_blueprints_ahead": 100, "max_blueprints_catchup": 50, "catchup_cooldown": 1 - } + }, + "sunset_sec": 0 }, "tx_pool_timeout_limit": "3600", "tx_pool_addr_limit": "4000", @@ -170,7 +172,8 @@ "max_blueprints_ahead": 100, "max_blueprints_catchup": 50, "catchup_cooldown": 1 - } + }, + "sunset_sec": 300 }, "observer": { "evm_node_endpoint": "hidden", diff --git a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out index b7ca8475be01..106d2d05e0bc 100644 --- a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out +++ b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- describe config.out @@ -59,7 +59,11 @@ /* The number of Layer 1 blocks the sequencer awaits before sending another batch of blueprints, as part of its catchup mechanism. */, - "dal_slots"?: [ integer ∈ [-128, 127] ... ] } }, + "dal_slots"?: [ integer ∈ [-128, 127] ... ] }, + "sunset_sec"?: + integer ∈ [0, 2^30] + /* Number of seconds prior to a sequencer operator upgrade before + which the current sequencer stops producing blocks */ }, "observer"?: { "evm_node_endpoint": $unistring diff --git a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- man.out b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- man.out index 491f6a33d651..2727ad79bedc 100644 --- a/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- man.out +++ b/etherlink/tezt/tests/expected/evm_sequencer.ml/EVM Node- man.out @@ -316,6 +316,7 @@ Run commands: [--genesis-timestamp <[TIMESTAMP]>] [--initial-kernel ] [-d --wallet-dir ] [-f --password-filename ] [--dal-slots ] + [--sunset-sec ] Start the EVM node in sequencer mode. --data-dir : The path to the EVM node data directory. Defaults to the value of the environment variable `$EVM_NODE_DATA_DIR` @@ -393,6 +394,9 @@ Run commands: -f --password-filename : path to the password filename --dal-slots : The DAL slots indices on which the sequencer is allowed to send blueprints. + --sunset-sec : Number of seconds prior to a sequencer operator + upgrade before which the current sequencer stops producing blocks + If set, defaults to the value of EVM_NODE_SEQUENCER_SUNSET_SEC. run observer [--data-dir ] [--config-file ] [--rpc-addr ] [--rpc-port ] [--rpc-batch-limit ] @@ -596,7 +600,7 @@ Configuration commands: [--catch-up-cooldown ] [--evm-node-endpoint ] [--history ] [--dont-track-rollup-node] [-d --wallet-dir ] [-f --force] [--dal-slots ] - [--network ] + [--network ] [--sunset-sec ] Create an initial config with default value. If the is set then adds the configuration for the proxy mode. If the is set,then adds the configuration @@ -687,6 +691,9 @@ Configuration commands: be `mainnet` or `testnet`. If set, some configuration options defaults to well-known values for the selected network. If set, defaults to the value of EVM_NODE_NETWORK. + --sunset-sec : Number of seconds prior to a sequencer operator + upgrade before which the current sequencer stops producing blocks + If set, defaults to the value of EVM_NODE_SEQUENCER_SUNSET_SEC. check config [--data-dir ] [--config-file ] [-p --print] [--network ] diff --git a/src/bin_testnet_scenarios/upgrade_etherlink.ml b/src/bin_testnet_scenarios/upgrade_etherlink.ml index c08f7870412e..1561981196d1 100644 --- a/src/bin_testnet_scenarios/upgrade_etherlink.ml +++ b/src/bin_testnet_scenarios/upgrade_etherlink.ml @@ -180,6 +180,7 @@ let prepare_and_run_sequencer rollup_node = tx_pool_addr_limit = None; tx_pool_tx_per_addr_limit = None; dal_slots = None; + sequencer_sunset_sec = None; }) (Sc_rollup_node.endpoint rollup_node) in diff --git a/tezt/tests/cloud/dal.ml b/tezt/tests/cloud/dal.ml index 453fcb4ccd8a..83b134a826ef 100644 --- a/tezt/tests/cloud/dal.ml +++ b/tezt/tests/cloud/dal.ml @@ -3677,6 +3677,7 @@ let init_etherlink_operator_setup cloud configuration etherlink_configuration tx_pool_addr_limit = None; tx_pool_tx_per_addr_limit = None; dal_slots; + sequencer_sunset_sec = None; } in let endpoint = Sc_rollup_node.endpoint sc_rollup_node in -- GitLab