diff --git a/tezt/tests/cloud/baker_helpers.ml b/tezt/tests/cloud/baker_helpers.ml index ceb6dba183a9038f029610018e97425b671da6bb..36db209a26e6ef6bb3df15e2c3c7c4e39e7e8cc2 100644 --- a/tezt/tests/cloud/baker_helpers.ml +++ b/tezt/tests/cloud/baker_helpers.ml @@ -7,7 +7,6 @@ (*****************************************************************************) open Agent_kind -open Scenarios_configuration open Scenarios_helpers open Tezos open Yes_crypto @@ -55,7 +54,7 @@ let init_baker ?stake ~configuration_stake ~data_dir ~simulate_network (* As simulate_network and stake are mutually exclusive, the stake is used only when the simulation is Disabled. *) match simulate_network with - | Disabled -> ( + | Network_simulation.Disabled -> ( match stake with | None -> return (List.nth configuration_stake i) | Some stake -> return stake) @@ -196,7 +195,7 @@ let init_bakers ~bakers ~stake ~data_dir ~simulate_network ~external_rpc (* As simulate_network and stake are mutually exclusive, the stake is used only when the simulation is Disabled. *) match simulate_network with - | Scatter (_, baker_count) -> + | Network_simulation.Scatter (_, baker_count) -> Lwt_list.mapi_s (fun i _ -> let name = name_of (Baker i) in diff --git a/tezt/tests/cloud/baker_helpers.mli b/tezt/tests/cloud/baker_helpers.mli index d62bd1950edc45d40bb19844e28e3ad6e6b514eb..a42d0c4a88e8e47c04a8ca9930966043c97c4d9e 100644 --- a/tezt/tests/cloud/baker_helpers.mli +++ b/tezt/tests/cloud/baker_helpers.mli @@ -37,7 +37,7 @@ val init_bakers : bakers:string list -> stake:int list Lwt.t -> data_dir:string option -> - simulate_network:Scenarios_configuration.network_simulation_config -> + simulate_network:Network_simulation.t -> external_rpc:bool -> network:Network.t -> snapshot:Snapshot_helpers.t -> diff --git a/tezt/tests/cloud/dal.ml b/tezt/tests/cloud/dal.ml index 695aaf390c3d1f22832a76dec64fa1296192370b..012b3f2dc3e00a2ec3bbfdb68533ecc3fb1d55ca 100644 --- a/tezt/tests/cloud/dal.ml +++ b/tezt/tests/cloud/dal.ml @@ -29,7 +29,7 @@ type configuration = { echo_rollup : bool; disconnect : (int * int) option; network : Network.t; - simulate_network : Scenarios_configuration.network_simulation_config; + simulate_network : Network_simulation.t; snapshot : Snapshot_helpers.t; bootstrap : bool; teztale : bool; @@ -1175,101 +1175,12 @@ let rec loop t level = let yes_wallet_exe = Uses.path Constant.yes_wallet -let parse_stake_arg ~stake_arg ~simulation_arg = - let open Network in - match simulation_arg with - | Scenarios_configuration.Disabled -> ( - match stake_arg with - | Custom distrib -> return distrib - | Mimic {network; max_nb_bakers} -> - let network_string = - match network with - | `Mainnet | `Ghostnet | `Rionet | `Seoulnet -> to_string network - | _ -> - failwith - (Format.sprintf - "Cannot get stake distribution for %s" - (to_string network)) - in - let endpoint = - Endpoint.make ~host:"rpc.tzkt.io" ~scheme:"https" ~port:443 () - in - let decoder json = JSON.(json |-> "cycle" |> as_int) in - let rpc = - RPC_core.( - make - GET - [ - network_string; - "chains"; - "main"; - "blocks"; - "head"; - "helpers"; - "current_level"; - ] - decoder) - in - let* response = RPC_core.call_raw endpoint rpc in - let cycle = - RPC_core.decode_raw ~origin:"Network.cycle" rpc response.body - in - let get_stake_in_ktez stake = - JSON.( - (stake |-> "frozen" |> as_int) + (stake |-> "delegated" |> as_int)) - / 1_000_000_000 - in - let decoder json = - json |> JSON.as_list - |> List.map (fun json_account -> - let active_stake = JSON.(json_account |-> "active_stake") in - get_stake_in_ktez active_stake) - in - let rpc = - RPC_core.( - make - GET - [ - network_string; - "chains"; - "main"; - "blocks"; - "head"; - "context"; - "raw"; - "json"; - "cycle"; - string_of_int cycle; - "selected_stake_distribution"; - ] - decoder) - in - let* response = RPC_core.call_raw endpoint rpc in - let distribution = - RPC_core.decode_raw ~origin:"Network.cycle" rpc response.body - in - let distribution = - match max_nb_bakers with - | None -> distribution - | Some n -> Tezos_stdlib.TzList.take_n n distribution - in - return distribution) - | Scatter _ | Map _ -> ( - match stake_arg with - | Custom [] -> - (* As simulate_network and stake are mutually exclusive, only empty - stake option is allowed. *) - Lwt.return [] - | _ -> - Test.fail - "Options --simulate and --stake are mutually exclusive. We cannot \ - set baker stake while using baking power of bakers from a \ - simulated network.") - let register (module Cli : Scenarios_cli.Dal) = let simulate_network = Cli.simulate_network in let stake = - parse_stake_arg ~stake_arg:Cli.stake ~simulation_arg:simulate_network + Stake_repartition.Dal.parse_arg + ~stake_arg:Cli.stake + ~simulation_arg:simulate_network in let configuration, etherlink_configuration = let stake_machine_type = Cli.stake_machine_type in @@ -1383,7 +1294,7 @@ let register (module Cli : Scenarios_cli.Dal) = toplog "Parsing CLI done" ; let baker_daemon_count = match simulate_network with - | Scenarios_configuration.Disabled -> 0 + | Network_simulation.Disabled -> 0 | Scatter (_selected_baker_count, baker_daemon_count) -> baker_daemon_count | Map ( _selected_baker_count, diff --git a/tezt/tests/cloud/dal_node_helpers.mli b/tezt/tests/cloud/dal_node_helpers.mli index b4c0ca46f22ff96455e5311f4a7637083784031c..28bdc749a008e63f772db8c6561bfcf5df8084ed 100644 --- a/tezt/tests/cloud/dal_node_helpers.mli +++ b/tezt/tests/cloud/dal_node_helpers.mli @@ -55,7 +55,7 @@ val init_producer_accounts : val init_producer : Cloud.t -> data_dir:string option -> - simulate_network:Scenarios_configuration.network_simulation_config -> + simulate_network:Network_simulation.t -> external_rpc:bool -> network:Network.t -> snapshot:Snapshot_helpers.t -> @@ -98,7 +98,7 @@ val producers_not_ready : producers:producer list -> bool val init_observer : Cloud.t -> data_dir:string option -> - simulate_network:Scenarios_configuration.network_simulation_config -> + simulate_network:Network_simulation.t -> external_rpc:bool -> network:Network.t -> snapshot:Snapshot_helpers.t -> diff --git a/tezt/tests/cloud/dal_reverse_proxy.mli b/tezt/tests/cloud/dal_reverse_proxy.mli index 5888af5e47d1fa076b6a6c5c4c32918e8380e46e..75a4a7d6340b1d855a14e358efe5dfd3a6ac2a03 100644 --- a/tezt/tests/cloud/dal_reverse_proxy.mli +++ b/tezt/tests/cloud/dal_reverse_proxy.mli @@ -31,7 +31,7 @@ val init_dal_reverse_proxy_observers : ppx_profiling_verbosity:string option -> ppx_profiling_backends:string list -> memtrace:bool -> - simulate_network:Scenarios_configuration.network_simulation_config -> + simulate_network:Network_simulation.t -> name_of:(int -> string) -> default_endpoint:string option -> node_p2p_endpoint:string -> diff --git a/tezt/tests/cloud/echo_rollup.mli b/tezt/tests/cloud/echo_rollup.mli index 3dd493e8f59eb5cb2cd374d4115a56f5c0ad87a0..97d4f18a1025bd80ddb2d273432872c293414636 100644 --- a/tezt/tests/cloud/echo_rollup.mli +++ b/tezt/tests/cloud/echo_rollup.mli @@ -28,7 +28,7 @@ val init_echo_rollup_account : val init_echo_rollup : Cloud.t -> data_dir:string option -> - simulate_network:Scenarios_configuration.network_simulation_config -> + simulate_network:Network_simulation.t -> external_rpc:bool -> network:Network.t -> snapshot:Snapshot_helpers.t -> diff --git a/tezt/tests/cloud/etherlink_helpers.mli b/tezt/tests/cloud/etherlink_helpers.mli index 783e2d14ddf5beeba3d557c1dc819600999ca1de..d9c1083a4b5276a9795b61008a382aef0f8a6f57 100644 --- a/tezt/tests/cloud/etherlink_helpers.mli +++ b/tezt/tests/cloud/etherlink_helpers.mli @@ -47,7 +47,7 @@ val init_etherlink_operators : DAL if enabled and a configurable number of producers. *) val init_etherlink : data_dir:string option -> - simulate_network:Scenarios_configuration.network_simulation_config -> + simulate_network:Network_simulation.t -> external_rpc:bool -> network:Network.t -> snapshot:Snapshot_helpers.t -> diff --git a/tezt/tests/cloud/layer1.ml b/tezt/tests/cloud/layer1.ml index b293dbe69f218fc884cfa8ddcc218df052b3139a..f332a42149abc77da1fd56987b0a9f3faac5b5f0 100644 --- a/tezt/tests/cloud/layer1.ml +++ b/tezt/tests/cloud/layer1.ml @@ -327,164 +327,6 @@ module Node = struct Lwt.return (client, accounts) end -(** Stresstest parameters - - [tps]: targeted number of transactions per second - - [seed]: seed used for stresstest traffic generation - *) -type stresstest_conf = {tps : int; seed : int} - -(** Scenario configuration - - - [snapshot]: local path or URL of the snapshot to use for the experiment. - local path implies to [scp] the snapshot to all the vms. - - - [stake]: stake repartition between baking nodes, numbers are relatives. - - [Manual [2,1,1]] runs 3 bakers, aggregate delegates from the network, - spreading them in 3 pools representing roughly 50%, 25% and 25% of the - total stake of the network. The same stake repartition using the same - snapshot will result in the same delegate repartition. - - There is a special case using a single number instead of a list, which - gives the number of bakers to use. Delegates will be distributed as - evenly as possible between these bakers. - - [Auto] will spawn one baker per delegate, and the list of delegates will - be automatically retrieved from the provided snapshot. - - - [maintenance_delay]: number of level which will be multiplied by the - position in the list of the bakers to define the store merge delay. - We want it to be the same for two runs with same parameters (not - random) and we want it not to occur at the same time on every baker. - Default value is 1. - Use 0 for disabling delay and have all the bakers to merge their - store at the beginning of cycles. - - - [migration_offset]: offset that dictates after how many levels a protocol - upgrade will be performed via a UAU. - - - [stresstest]: See the description of [stresstest_conf] - *) -type configuration = { - stake : Scenarios_cli.stake; - network : Network.t; - snapshot : Snapshot_helpers.t; - stresstest : stresstest_conf option; - without_dal : bool; - dal_node_producers : int list option; - maintenance_delay : int; - migration_offset : int option; - ppx_profiling_verbosity : string option; - ppx_profiling_backends : string list; - signing_delay : (float * float) option; - fixed_random_seed : int option; -} - -let stresstest_encoding = - let open Data_encoding in - conv - (fun {tps; seed} -> (tps, seed)) - (fun (tps, seed) -> {tps; seed}) - (obj2 (req "tps" int31) (req "seed" int31)) - -let stake_encoding = - let open Data_encoding in - union - [ - case - (Tag 0) - ~title:"auto" - (constant "auto") - (function Scenarios_cli.Auto -> Some () | _ -> None) - (fun () -> Scenarios_cli.Auto); - case - (Tag 1) - ~title:"manual" - (list int31) - (function Scenarios_cli.Manual d -> Some d | _ -> None) - (fun d -> Manual d); - ] - -let configuration_encoding = - let open Data_encoding in - conv - (fun { - stake; - network; - snapshot; - stresstest; - without_dal; - dal_node_producers; - maintenance_delay; - migration_offset; - ppx_profiling_verbosity; - ppx_profiling_backends; - signing_delay; - fixed_random_seed; - } - -> - ( ( stake, - network, - snapshot, - stresstest, - without_dal, - dal_node_producers, - maintenance_delay, - migration_offset, - ppx_profiling_verbosity, - ppx_profiling_backends ), - (signing_delay, fixed_random_seed) )) - (fun ( ( stake, - network, - snapshot, - stresstest, - without_dal, - dal_node_producers, - maintenance_delay, - migration_offset, - ppx_profiling_verbosity, - ppx_profiling_backends ), - (signing_delay, fixed_random_seed) ) - -> - { - stake; - network; - snapshot; - stresstest; - without_dal; - dal_node_producers; - maintenance_delay; - migration_offset; - ppx_profiling_verbosity; - ppx_profiling_backends; - signing_delay; - fixed_random_seed; - }) - (merge_objs - (obj10 - (dft "stake" stake_encoding Scenarios_cli.(Manual [1])) - (req "network" Network.encoding) - (req "snapshot" Snapshot_helpers.encoding) - (opt "stresstest" stresstest_encoding) - (dft - "without_dal" - bool - Scenarios_cli.Layer1_default.default_without_dal) - (opt "dal_node_producers" (list int31)) - (dft - "maintenance_delay" - int31 - Scenarios_cli.Layer1_default.default_maintenance_delay) - (opt "migration_offset" int31) - (opt "ppx_profiling_verbosity" string) - (dft - "ppx_profiling_backends" - (list string) - Scenarios_cli.Layer1_default.default_ppx_profiling_backends)) - (obj2 - (opt "Signing_delay" (tup2 float float)) - (opt "fixed_random_seed" int31))) - type bootstrap = { agent : Agent.t; node : Node.t; @@ -510,7 +352,7 @@ type stresstester = { } type 'network t = { - configuration : configuration; + configuration : Scenarios_configuration.LAYER1.t; cloud : Cloud.t; bootstrap : bootstrap; bakers : baker list; @@ -518,8 +360,9 @@ type 'network t = { stresstesters : stresstester list; } -let init_baker_i i (configuration : configuration) cloud ~peers - dal_node_p2p_endpoint (accounts : baker_account list) (agent, node, name) = +let init_baker_i i (configuration : Scenarios_configuration.LAYER1.t) cloud + ~peers dal_node_p2p_endpoint (accounts : baker_account list) + (agent, node, name) = let delay = i * configuration.maintenance_delay in let* client = toplog "init_baker: Initialize node" ; @@ -605,8 +448,8 @@ let init_baker_i i (configuration : configuration) cloud ~peers in Lwt.return {agent; node; dal_node; baker; accounts} -let init_producer_i i (configuration : configuration) slot_index - (account : Account.key) cloud ~peers dal_node_p2p_endpoint +let init_producer_i i (configuration : Scenarios_configuration.LAYER1.t) + slot_index (account : Account.key) cloud ~peers dal_node_p2p_endpoint (agent, node, name) = let delay = i * configuration.maintenance_delay in let () = toplog "Initializing the DAL producer %s" name in @@ -661,8 +504,8 @@ let fund_stresstest_accounts ~source client = ~initial_amount:(Tez.of_mutez_int64 1_000_000_000L) client -let init_stresstest_i i (configuration : configuration) ~pkh ~pk ~peers - (agent, node, name) tps : stresstester Lwt.t = +let init_stresstest_i i (configuration : Scenarios_configuration.LAYER1.t) ~pkh + ~pk ~peers (agent, node, name) tps : stresstester Lwt.t = let delay = i * configuration.maintenance_delay in let* client, accounts = toplog "init_stresstest: Initialize node" ; @@ -681,8 +524,8 @@ let init_stresstest_i i (configuration : configuration) ~pkh ~pk ~peers in Lwt.return {agent; node; client; accounts} -let init_network ~peers (configuration : configuration) cloud teztale - ((agent, node, _) as resources) = +let init_network ~peers (configuration : Scenarios_configuration.LAYER1.t) cloud + teztale ((agent, node, _) as resources) = toplog "init_network: Initializing the bootstrap node" ; let* client, delegates, rich_account = Node.init_bootstrap_node @@ -834,7 +677,8 @@ let distribute_delegates stake (baker_accounts : (baker_account * int) list) = |> print_list "Distribution" ; List.map (List.map fst) distribution -let init ~(configuration : configuration) cloud = +let init ~(configuration : Scenarios_configuration.LAYER1.t) cloud = + let open Scenarios_configuration.LAYER1 in let () = toplog "Init" in (* First, we allocate agents and node address/port in order to have the peer list known when initializing. *) @@ -933,7 +777,7 @@ let init ~(configuration : configuration) cloud = let slot_index = extract_agent_index agent_name_dal_producer agent in init_producer_i i - (configuration : configuration) + configuration slot_index account cloud @@ -1227,72 +1071,23 @@ let number_of_bakers ~snapshot ~(network : Network.t) = Lwt.return n let register (module Cli : Scenarios_cli.Layer1) = - let configuration : configuration = - let stake = Cli.stake in - let network = Cli.network in - let stresstest = - Option.map (fun (tps, seed) -> {tps; seed}) Cli.stresstest - in - let dal_node_producers = Cli.dal_producers_slot_indices in - let maintenance_delay = Cli.maintenance_delay in - let snapshot = Cli.snapshot in - let without_dal = Cli.without_dal in - let migration_offset = Cli.migration_offset in - let ppx_profiling_verbosity = Cli.ppx_profiling_verbosity in - let ppx_profiling_backends = Cli.ppx_profiling_backends in - let signing_delay = Cli.signing_delay in - let fixed_random_seed = Cli.fixed_random_seed in - match Tezt_cloud_cli.scenario_specific_json with - | Some ("LAYER1", json) -> - let conf = Data_encoding.Json.destruct configuration_encoding json in - { - stake = Option.value ~default:conf.stake stake; - network = Option.value ~default:conf.network network; - snapshot = Option.value ~default:conf.snapshot snapshot; - without_dal = conf.without_dal || without_dal; - stresstest = - (if stresstest <> None then stresstest else conf.stresstest); - dal_node_producers = - (if dal_node_producers <> None then dal_node_producers - else conf.dal_node_producers); - maintenance_delay = - Option.value ~default:conf.maintenance_delay maintenance_delay; - migration_offset = - (if migration_offset <> None then migration_offset - else conf.migration_offset); - ppx_profiling_verbosity = - (if ppx_profiling_verbosity <> None then ppx_profiling_verbosity - else conf.ppx_profiling_verbosity); - ppx_profiling_backends = - (if ppx_profiling_backends <> [] then ppx_profiling_backends - else conf.ppx_profiling_backends); - signing_delay = - (if signing_delay <> None then signing_delay else conf.signing_delay); - fixed_random_seed = - (if fixed_random_seed <> None then fixed_random_seed - else conf.fixed_random_seed); - } - | _ -> - { - stake = Option.value ~default:(Manual [1]) stake; - network = Option.get network; - stresstest; - maintenance_delay = - Option.value - ~default:Scenarios_cli.Layer1_default.default_maintenance_delay - maintenance_delay; - snapshot = Option.get snapshot; - without_dal; - dal_node_producers; - migration_offset; - ppx_profiling_verbosity; - ppx_profiling_backends; - signing_delay; - fixed_random_seed; - } + let configuration : Scenarios_configuration.LAYER1.t = + { + stake = Cli.stake; + network = Cli.network; + snapshot = Cli.snapshot; + stresstest = Cli.stresstest; + without_dal = Cli.without_dal; + dal_node_producers = Cli.dal_producers_slot_indices; + maintenance_delay = Cli.maintenance_delay; + migration_offset = Cli.migration_offset; + ppx_profiling_verbosity = Cli.ppx_profiling_verbosity; + ppx_profiling_backends = Cli.ppx_profiling_backends; + signing_delay = Cli.signing_delay; + fixed_random_seed = Cli.fixed_random_seed; + octez_release = Cli.octez_release; + } in - if configuration.snapshot = Snapshot_helpers.No_snapshot then - Test.fail "snapshot parameter cannot be empty" ; let with_dal_producers = match configuration.dal_node_producers with | None | Some [] -> false @@ -1307,7 +1102,7 @@ let register (module Cli : Scenarios_cli.Layer1) = let default_docker_image = Option.map (fun tag -> Agent.Configuration.Octez_release {tag}) - Cli.octez_release + configuration.octez_release in let default_vm_configuration ~name = Agent.Configuration.make ?docker_image:default_docker_image ~name () @@ -1335,13 +1130,13 @@ let register (module Cli : Scenarios_cli.Layer1) = let init n = List.init n (fun i -> `Baker i) in let* bakers = match configuration.stake with - | Scenarios_cli.Auto -> + | Stake_repartition.Layer1.Auto -> Lwt.map init (number_of_bakers ~snapshot:configuration.snapshot ~network:configuration.network) - | Scenarios_cli.Manual stake -> Lwt.return (init (List.length stake)) + | Manual stake -> Lwt.return (init (List.length stake)) in Lwt.return @@ `Bootstrap diff --git a/tezt/tests/cloud/network.ml b/tezt/tests/cloud/network.ml index 2a6713e6f08c74185d60f330ec7170ab768f954f..76845ad08843dd8a62dfc1a77dcb0e68dba7021b 100644 --- a/tezt/tests/cloud/network.ml +++ b/tezt/tests/cloud/network.ml @@ -68,30 +68,6 @@ let encoding = | None -> invalid_arg ("Network.encoding: invalid network: " ^ s)) string -type stake_repartition = - | Custom of int list - | Mimic of {network : public; max_nb_bakers : int option} - -let stake_repartition_encoding = - let open Data_encoding in - union - [ - case - (Tag 1) - ~title:"custom" - (list int31) - (function Custom l -> Some l | _ -> None) - (fun l -> Custom l); - case - (Tag 2) - ~title:"mimic" - (obj2 (req "network" public_encoding) (opt "max_nb_bakers" int31)) - (function - | Mimic {network; max_nb_bakers} -> Some (network, max_nb_bakers) - | _ -> None) - (fun (network, max_nb_bakers) -> Mimic {network; max_nb_bakers}); - ] - let default_protocol : t -> Protocol.t = function | `Mainnet -> R022 | `Ghostnet -> R022 diff --git a/tezt/tests/cloud/network.mli b/tezt/tests/cloud/network.mli index 8ecf9139f0c3a52cf27df5d04213dc55c3a2d00c..4828d348ec737aee4ae0c7f26e1c06573f0e7def 100644 --- a/tezt/tests/cloud/network.mli +++ b/tezt/tests/cloud/network.mli @@ -24,13 +24,9 @@ type t = [public | `Sandbox] (** ["mainnet" | "ghostnet" | "rionet" | "seoulnet" | "nextnet-%s" | "weeklynet-%s" | "sandbox"] *) val to_string : [< t] -> string -val encoding : t Data_encoding.t - -type stake_repartition = - | Custom of int list - | Mimic of {network : public; max_nb_bakers : int option} +val public_encoding : public Data_encoding.t -val stake_repartition_encoding : stake_repartition Data_encoding.t +val encoding : t Data_encoding.t val is_public : t -> bool diff --git a/tezt/tests/cloud/network_simulation.ml b/tezt/tests/cloud/network_simulation.ml new file mode 100644 index 0000000000000000000000000000000000000000..6b3fcfe7c3bee369e43829aac6d4281606323737 --- /dev/null +++ b/tezt/tests/cloud/network_simulation.ml @@ -0,0 +1,100 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs, *) +(* Copyright (c) 2025 Trilitech *) +(* *) +(*****************************************************************************) + +type t = Scatter of int * int | Map of int * int * int | Disabled + +let encoding = + let open Data_encoding in + union + [ + case + (Tag 1) + ~title:"scatter" + (tup2 int31 int31) + (function + | Scatter (nb_keys, nb_daemons) -> Some (nb_keys, nb_daemons) + | _ -> None) + (fun (nb_keys, nb_daemons) -> Scatter (nb_keys, nb_daemons)); + case + (Tag 2) + ~title:"map" + (tup3 int31 int31 int31) + (function + | Map (nb_keys, nb_alone_bakers, nb_additional_daemons) -> + Some (nb_keys, nb_alone_bakers, nb_additional_daemons) + | _ -> None) + (fun (nb_keys, nb_alone_bakers, nb_additional_daemons) -> + Map (nb_keys, nb_alone_bakers, nb_additional_daemons)); + case + (Tag 3) + ~title:"disabled" + empty + (function Disabled -> Some () | _ -> None) + (fun () -> Disabled); + ] + +let to_string = function + | Scatter (x, y) -> Format.sprintf "scatter(%d,%d)" x y + | Map (x, y, z) -> Format.sprintf "map(%d,%d,%d)" x y z + | Disabled -> Format.sprintf "disabled" + +let parse simulate_network_arg = + let is_positive_param p = + if p > 0 then p + else + Test.fail + "Unexpected value provided, [%d], from argument [%s]. Values must be \ + positive integers.@." + p + simulate_network_arg + in + let is_arg1_sup_eq_arg2 arg1 arg2 = + if arg1 >= arg2 then () + else + Test.fail + "Unexpected value provided for argument [%s]. %d must be greater or \ + equal to %d." + simulate_network_arg + arg1 + arg2 + in + let re_scatter = Str.regexp "\\(scatter\\)(\\([^,]+\\),\\([^)]*\\))" in + let re_map = Str.regexp "\\(map\\)(\\([^,]+\\),\\([^)]*\\),\\([^)]*\\))" in + if Str.string_match re_scatter simulate_network_arg 0 then + let arg1 = + Str.matched_group 2 simulate_network_arg + |> int_of_string |> is_positive_param + in + let arg2 = + Str.matched_group 3 simulate_network_arg + |> int_of_string |> is_positive_param + in + let () = is_arg1_sup_eq_arg2 arg1 arg2 in + Some (Scatter (arg1, arg2)) + else if Str.string_match re_map simulate_network_arg 0 then + let arg1 = + Str.matched_group 2 simulate_network_arg + |> int_of_string |> is_positive_param + in + let arg2 = + Str.matched_group 3 simulate_network_arg + |> int_of_string |> is_positive_param + in + let arg3 = + Str.matched_group 4 simulate_network_arg + |> int_of_string |> is_positive_param + in + let () = is_arg1_sup_eq_arg2 arg1 (arg2 + arg3) in + Some (Map (arg1, arg2, arg3)) + else + Test.fail + "Unexpected network simulation config (--simulation) [%s]" + simulate_network_arg + +let typ = + Clap.typ ~name:"simulate_network" ~dummy:Disabled ~parse ~show:to_string diff --git a/tezt/tests/cloud/network_simulation.mli b/tezt/tests/cloud/network_simulation.mli new file mode 100644 index 0000000000000000000000000000000000000000..489e66f1d9121d60ba191752fe0eb2fdafbdabd6 --- /dev/null +++ b/tezt/tests/cloud/network_simulation.mli @@ -0,0 +1,31 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs, *) +(* Copyright (c) 2025 Trilitech *) +(* *) +(*****************************************************************************) + +(** [t] allows to configure the simulation of a network, relying on the actual + distribution of rights that will be found in the imported data (data-dir or + snapshot). It requires yes crypto to be enabled. + The simulate option has three modes: + - scatter(x,y): selects the [x] biggest bakers found, and scatters their + baking rights, in a round robin fashion, on [y] baker daemons. This is + particularly useful to scatter the baking power across several baker + daemons, + - map(x,y,z): maps [y] keys from the biggest bakers found onto [y] baker + daemons (theses daemons are handling a single key) and scatters the + remaining [x-y] keys to [z] baker daemons. This is particularly useful to + simulate the behaviour of an actual network, + - disabled: no simulation, we rely on the configuration.stake parameter. + For example: + - scatter(10,2): [[0;2;4;6;8];[1;3;5;7;9]] + - map(10,2,1):[[0];[1];[2;3;4;5;6;7;8;9]] + - map(10,2,2):[[0];[1];[2;5;6;8];[3;5;8;9]] *) + +type t = Scatter of int * int | Map of int * int * int | Disabled + +val encoding : t Data_encoding.t + +val typ : t Clap.typ diff --git a/tezt/tests/cloud/scenarios_cli.ml b/tezt/tests/cloud/scenarios_cli.ml index e8b4649d678eeaaf9903f5c5063858b6b6370bef..69ed6e6c7b50dff339f53c8a8f5378e1d38fc4b4 100644 --- a/tezt/tests/cloud/scenarios_cli.ml +++ b/tezt/tests/cloud/scenarios_cli.ml @@ -20,59 +20,6 @@ module Clap = struct let list_of_int ?dummy name = list ~name ?dummy int_of_string string_of_int end -let parse_network_simulation_config_from_args simulate_network_arg = - let is_positive_param p = - if p > 0 then p - else - Test.fail - "Unexpected value provided, [%d], from argument [%s]. Values must be \ - positive integers.@." - p - simulate_network_arg - in - let is_arg1_sup_eq_arg2 arg1 arg2 = - if arg1 >= arg2 then () - else - Test.fail - "Unexpected value provided for argument [%s]. %d must be greater or \ - equal to %d." - simulate_network_arg - arg1 - arg2 - in - let re_scatter = Str.regexp "\\(scatter\\)(\\([^,]+\\),\\([^)]*\\))" in - let re_map = Str.regexp "\\(map\\)(\\([^,]+\\),\\([^)]*\\),\\([^)]*\\))" in - if Str.string_match re_scatter simulate_network_arg 0 then - let arg1 = - Str.matched_group 2 simulate_network_arg - |> int_of_string |> is_positive_param - in - let arg2 = - Str.matched_group 3 simulate_network_arg - |> int_of_string |> is_positive_param - in - let () = is_arg1_sup_eq_arg2 arg1 arg2 in - Some (Scenarios_configuration.Scatter (arg1, arg2)) - else if Str.string_match re_map simulate_network_arg 0 then - let arg1 = - Str.matched_group 2 simulate_network_arg - |> int_of_string |> is_positive_param - in - let arg2 = - Str.matched_group 3 simulate_network_arg - |> int_of_string |> is_positive_param - in - let arg3 = - Str.matched_group 4 simulate_network_arg - |> int_of_string |> is_positive_param - in - let () = is_arg1_sup_eq_arg2 arg1 (arg2 + arg3) in - Some (Scenarios_configuration.Map (arg1, arg2, arg3)) - else - Test.fail - "Unexpected network simulation config (--simulation) [%s]" - simulate_network_arg - let network_typ : Network.t Clap.typ = Clap.typ ~name:"network" @@ -100,16 +47,13 @@ module type Dal = sig val network : Network.t - val simulate_network_typ : - Scenarios_configuration.network_simulation_config Clap.typ - - val simulate_network : Scenarios_configuration.network_simulation_config + val simulate_network : Network_simulation.t val snapshot : Snapshot_helpers.t val bootstrap : bool - val stake : Network.stake_repartition + val stake : Stake_repartition.Dal.t val bakers : string list @@ -269,14 +213,6 @@ module Dal () : Dal = struct network_typ (Option.value ~default:`Sandbox config.network) - let simulate_network_typ : - Scenarios_configuration.network_simulation_config Clap.typ = - Clap.typ - ~name:"simulate_network" - ~dummy:Scenarios_configuration.Disabled - ~parse:parse_network_simulation_config_from_args - ~show:Scenarios_configuration.simulate_network_to_string - let simulate_network = Clap.default ~section @@ -301,7 +237,7 @@ module Dal () : Dal = struct For example:\n\ - scatter(10,2): [[0;2;4;6;8];[1;3;5;7;9]]\n\ - map(10,3):[[0];[1];[2;3;4;5;6;7;8;9]]" - simulate_network_typ + Network_simulation.typ (Option.value ~default:Disabled config.simulate_network) let snapshot = @@ -321,43 +257,8 @@ module Dal () : Dal = struct (let default = match network with `Sandbox -> true | _ -> false in Option.value ~default config.bootstrap) - let stake_repartition_typ : Network.stake_repartition Clap.typ = - let open Network in - let parse_public_network (net : string) : public option = - try Option.map to_public (parse net) with _ -> None - in - Clap.typ - ~name:"stake_repartition" - ~dummy:(Custom [100]) - ~parse:(fun str -> - (* If it is a list of int, then a custom repartition has been selected. *) - let int_list_regexp = Str.regexp {|\([0-9]+,\( ?\)\)*[0-9]+$|} in - if Str.string_match int_list_regexp str 0 then - Some - (Custom (str |> String.split_on_char ',' |> List.map int_of_string)) - (* Else we expect a network name, potentially followed by how many bakers should be created. *) - else - match String.split_on_char '_' str with - | [network] -> - Option.map - (fun network -> Mimic {network; max_nb_bakers = None}) - (parse_public_network network) - | [network; n_str] -> ( - try - let n = int_of_string n_str in - Option.map - (fun network -> Mimic {network; max_nb_bakers = Some n}) - (parse_public_network network) - with _ -> None) - | _ -> None) - ~show:(function - | Custom l -> - l |> List.map string_of_int |> String.concat (String.make 1 ',') - | Mimic {network; max_nb_bakers = None} -> to_string network - | Mimic {network; max_nb_bakers = Some n} -> - Format.sprintf "%s_%d" (to_string network) n) - let stake = + let open Stake_repartition.Dal in Clap.default ~section ~long:"stake" @@ -368,10 +269,10 @@ module Dal () : Dal = struct total stake is proportional to the sum of all shares. If a network is \ provided share repartitions is the same as on this network (truncated \ to the N biggest delegates if _ is given)." - stake_repartition_typ + typ (let default = - if network = `Sandbox && simulate_network = Disabled then - Network.Custom [100] + if network = `Sandbox && simulate_network = Network_simulation.Disabled + then Custom [100] else Custom [] in Option.value ~default config.stake) @@ -779,16 +680,14 @@ module Dal () : Dal = struct Option.fold ~none:config.number_of_slots ~some:Option.some from_cli end -type stake = Auto | Manual of int list - module type Layer1 = sig - val network : Network.t option + val network : Network.t - val stake : stake option + val stake : Stake_repartition.Layer1.t - val stresstest : (int * int) option + val stresstest : Stresstest.t option - val maintenance_delay : int option + val maintenance_delay : int val migration_offset : int option @@ -796,7 +695,7 @@ module type Layer1 = sig val fixed_random_seed : int option - val snapshot : Snapshot_helpers.t option + val snapshot : Snapshot_helpers.t val octez_release : string option @@ -811,55 +710,89 @@ module type Layer1 = sig val ppx_profiling_backends : string list end -module Layer1_default = struct - let default_maintenance_delay = 1 - - let default_ppx_profiling_backends = ["txt"] - - let default_without_dal = false -end - module Layer1 () = struct - (** Keep the CLI arguments optional because they can be defined in a - config file. Parameters consistency will be checked in the Layer1 - scenario. *) + let scenario_name = "LAYER1" let section = Clap.section ~description: - "All the options related to running Layer 1 scenarios onto the cloud. \ - Note that in order to run these tests, you need to specify both \ - [cloud] and [layer1] tags on the command line" + "All the options related to running Layer 1 scenarios onto the cloud." "LAYER1" - let network : Network.t option = - Clap.optional + let config = + match Tezt_cloud_cli.scenario_specific_json with + | None -> None + | Some (name, options) when name = scenario_name -> ( + try + Data_encoding.Json.destruct + Scenarios_configuration.LAYER1.encoding + options + |> Option.some + with + | Json_encoding.Cannot_destruct (_, e) as exn -> + Log.error + "Cannot load config file: %s - %s" + (Printexc.to_string exn) + (Printexc.to_string e) ; + raise exn + | e -> raise e) + | Some (name, _options) -> + Log.error + "Configuration file mismatch. This config file is for scenario %s \ + whereas the command was launched for scenario %s" + name + scenario_name ; + raise Scenario_mismatch + + let mandatory_from_cli_or_config (type a) (typ : a Clap.typ) ?section ?last + ?long ?long_synonyms ?short ?short_synonyms ?placeholder ?description + ~from_config () = + (* If the config exists, then the parameter is available as it is required + in the config. In that case, the parameter is optional. Otherwise, it is + mandatory. *) + match Option.map from_config config with + | Some config_param -> ( + let from_cli = + Clap.optional + ?section + ?last + ?long + ?long_synonyms + ?short + ?short_synonyms + ?placeholder + ?description + typ + () + in + match from_cli with None -> config_param | Some p -> p) + | None -> + Clap.mandatory + ?section + ?last + ?long + ?long_synonyms + ?short + ?short_synonyms + ?placeholder + ?description + typ + () + + let network = + mandatory_from_cli_or_config ~section ~long:"network" - ~placeholder:"" + ~placeholder: + " \ + (sandbox,ghostnet,nextnet-YYYY-MM-DD,weeklynet-YYYY-MM-DD,...)" ~description:"Allow to specify a network to use for the scenario" network_typ + ~from_config:(fun config -> config.network) () - let stake : stake option = - let typ = - let parse = function - | "AUTO" | "auto" -> Some Auto - | string -> ( - try - match string |> String.split_on_char ',' with - | [n] -> Some (Manual (List.init (int_of_string n) (fun _ -> 1))) - | distribution -> - Some (Manual (List.map int_of_string distribution)) - with exn -> raise exn) - in - let show = function - | Auto -> "AUTO" - | Manual dist -> List.map string_of_int dist |> String.concat "," - in - Clap.typ ~name:"stake" ~dummy:(Manual [1]) ~parse ~show - in - Clap.optional + let stake = + mandatory_from_cli_or_config ~section ~long:"stake" ~placeholder:"AUTO||,,...," @@ -871,36 +804,42 @@ module Layer1 () = struct a list of relative weight. Delegates will be distributed amongst \ pools in order to (approximately) respect the given stake \ distribution." - typ + Stake_repartition.Layer1.typ + ~from_config:(fun config -> config.stake) () let stresstest = - let typ = - let parse string = - try - match string |> String.split_on_char '/' with - | [n] -> Some (int_of_string n, Random.int 1073741823 (* 2^30 -1 *)) - | [n; seed] -> Some (int_of_string n, int_of_string seed) - | _ -> None - with exn -> raise exn - in - let show (tps, seed) = string_of_int tps ^ "/" ^ string_of_int seed in - Clap.typ ~name:"stresstest" ~dummy:(0, 0) ~parse ~show + let from_cli = + Clap.optional + ~section + ~long:"stresstest" + ~placeholder:"TPS[/seed]" + ~description: + "A Public key hash and its public key are automatically retrieved \ + from the yes wallet to fund fresh accounts for reaching TPS \ + stresstest traffic generation. A seed for stresstest initialization \ + can also be specified." + Stresstest.typ + () in - Clap.optional - ~section - ~long:"stresstest" - ~placeholder:"TPS[/seed]" - ~description: - "A Public key hash and its public key are automatically retrieved from \ - the yes wallet to fund fresh accounts for reaching TPS stresstest \ - traffic generation. A seed for stresstest initialization can also be \ - specified." - typ - () + let from_config = + Option.fold + ~none:None + ~some:(fun (c : Scenarios_configuration.LAYER1.t) -> c.stresstest) + config + in + Option.fold ~none:from_config ~some:Option.some from_cli let maintenance_delay = - Clap.optional_int + let default_maintenance_delay = + Scenarios_configuration.LAYER1.Default.maintenance_delay + in + let from_config = + Option.map + (fun (c : Scenarios_configuration.LAYER1.t) -> c.maintenance_delay) + config + in + Clap.default_int ~section ~long:"maintenance-delay" ~placeholder:"N" @@ -908,17 +847,26 @@ module Layer1 () = struct (sf "Each baker has maintenance delayed by (position in the list * N). \ Default is %d. Use 0 for disabling mainteance delay" - Layer1_default.default_maintenance_delay) - () + default_maintenance_delay) + (Option.value ~default:default_maintenance_delay from_config) let migration_offset = - Clap.optional_int - ~section - ~long:"migration-offset" - ~description: - "After how many levels we will perform a UAU to upgrade to the next \ - protocol." - () + let from_cli = + Clap.optional_int + ~section + ~long:"migration-offset" + ~description: + "After how many levels we will perform a UAU to upgrade to the next \ + protocol." + () + in + let from_config = + Option.fold + ~none:None + ~some:(fun (c : Scenarios_configuration.LAYER1.t) -> c.migration_offset) + config + in + Option.fold ~none:from_config ~some:Option.some from_cli let signing_delay = let typ = @@ -933,45 +881,74 @@ module Layer1 () = struct let show (min, max) = Format.sprintf "%f,%f" min max in Clap.typ ~name:"signing-delay" ~dummy:(0., 0.) ~parse ~show in - Clap.optional - ~section - ~long:"signing-delay" - ~placeholder:"," - ~description: - "Introduce a random signing delay between and seconds. \ - This is useful when simulating a network with multiple bakers to \ - avoid having all bakers trying to sign at the same time. If only one \ - value is provided, the minimum is set to 0." - typ - () + let from_cli = + Clap.optional + ~section + ~long:"signing-delay" + ~placeholder:"," + ~description: + "Introduce a random signing delay between and seconds. \ + This is useful when simulating a network with multiple bakers to \ + avoid having all bakers trying to sign at the same time. If only \ + one value is provided, the minimum is set to 0." + typ + () + in + let from_config = + Option.fold + ~none:None + ~some:(fun (c : Scenarios_configuration.LAYER1.t) -> c.signing_delay) + config + in + Option.fold ~none:from_config ~some:Option.some from_cli let fixed_random_seed = - Clap.optional_int - ~section - ~long:"fixed-seed" - ~description: - "Use a fixed seed for the client/baker random number generator. This \ - can be useful for reproducing an experiment." - () + let from_cli = + Clap.optional_int + ~section + ~long:"fixed-seed" + ~description: + "Use a fixed seed for the client/baker random number generator. This \ + can be useful for reproducing an experiment." + () + in + let from_config = + Option.fold + ~none:None + ~some:(fun (c : Scenarios_configuration.LAYER1.t) -> + c.fixed_random_seed) + config + in + Option.fold ~none:from_config ~some:Option.some from_cli let snapshot = - Clap.optional + mandatory_from_cli_or_config ~section ~long:"snapshot" ~description: - "Either a path the a local file or url of the snapshot to use for \ - bootstrapping the experiment" + "Snapshot file, which is stored locally, to initiate the scenario with \ + some data" snapshot_typ + ~from_config:(fun c -> c.snapshot) () let octez_release = - Clap.optional_string - ~section - ~long:"octez-release" - ~placeholder:"" - ~description: - "Use the octez release instead of local octez binaries." - () + let from_cli = + Clap.optional_string + ~section + ~long:"octez-release" + ~placeholder:"" + ~description: + "Use the octez release instead of local octez binaries." + () + in + let from_config = + Option.fold + ~none:None + ~some:(fun (c : Scenarios_configuration.LAYER1.t) -> c.octez_release) + config + in + Option.fold ~none:from_config ~some:Option.some from_cli let vms_config = Clap.optional_string @@ -983,35 +960,71 @@ module Layer1 () = struct () let without_dal = + let from_config = + Option.map + (fun (c : Scenarios_configuration.LAYER1.t) -> c.without_dal) + config + in Clap.flag ~section ~set_long:"without-dal" ~description: "Disable running DAL nodes on bootstrap and bakers nodes. It is set to \ `false` by default." - Layer1_default.default_without_dal + (Option.value + ~default:Scenarios_configuration.LAYER1.Default.without_dal + from_config) let dal_producers_slot_indices = - Clap.optional - ~section - ~long:"producer-slot-indices" - ~description: - "Specify the slot indices for DAL producers to run. The number of DAL \ - producers run is the size of the list." - (Clap.list_of_int ~dummy:[] "producer_slot_indices") - () + let from_cli = + Clap.optional + ~section + ~long:"producer-slot-indices" + ~description: + "Specify the slot indices for DAL producers to run. The number of \ + DAL producers run is the size of the list." + (Clap.list_of_int ~dummy:[] "producer_slot_indices") + () + in + let from_config = + Option.fold + ~none:None + ~some:(fun (c : Scenarios_configuration.LAYER1.t) -> + c.dal_node_producers) + config + in + Option.fold ~none:from_config ~some:Option.some from_cli let ppx_profiling_verbosity = - Clap.optional_string - ~section - ~long:"ppx-profiling-verbosity" - ~description: - "Enable PPX profiling on all components, with the given level of \ - verbosity. " - () + let from_cli = + Clap.optional_string + ~section + ~long:"ppx-profiling-verbosity" + ~description: + "Enable PPX profiling on all components, with the given level of \ + verbosity. " + () + in + let from_config = + Option.fold + ~none:None + ~some:(fun (c : Scenarios_configuration.LAYER1.t) -> + c.ppx_profiling_verbosity) + config + in + Option.fold ~none:from_config ~some:Option.some from_cli let ppx_profiling_backends = - let default = Layer1_default.default_ppx_profiling_backends in + let default = + Scenarios_configuration.LAYER1.Default.ppx_profiling_backends + in + let from_config = + Option.fold + ~none:default + ~some:(fun (c : Scenarios_configuration.LAYER1.t) -> + c.ppx_profiling_backends) + config + in let from_cli = Clap.list_string ~section @@ -1024,10 +1037,7 @@ module Layer1 () = struct (String.concat "," default)) () in - Option.fold - ~none:default - ~some:Fun.id - (match from_cli with [] -> None | _ -> Some from_cli) + from_config @ from_cli end module type Tezlink = sig diff --git a/tezt/tests/cloud/scenarios_configuration.ml b/tezt/tests/cloud/scenarios_configuration.ml index 6d4bcb824900529ae8dab8174c3ec717a71244e7..6d9b84c92ec4bd56564255dc32a0edfa6ac2b5e2 100644 --- a/tezt/tests/cloud/scenarios_configuration.ml +++ b/tezt/tests/cloud/scenarios_configuration.ml @@ -5,56 +5,16 @@ (* *) (*****************************************************************************) -type network_simulation_config = - | Scatter of int * int - | Map of int * int * int - | Disabled - -let network_simulation_config_encoding = - let open Data_encoding in - union - [ - case - (Tag 1) - ~title:"scatter" - (tup2 int31 int31) - (function - | Scatter (nb_keys, nb_daemons) -> Some (nb_keys, nb_daemons) - | _ -> None) - (fun (nb_keys, nb_daemons) -> Scatter (nb_keys, nb_daemons)); - case - (Tag 2) - ~title:"map" - (tup3 int31 int31 int31) - (function - | Map (nb_keys, nb_alone_bakers, nb_additional_daemons) -> - Some (nb_keys, nb_alone_bakers, nb_additional_daemons) - | _ -> None) - (fun (nb_keys, nb_alone_bakers, nb_additional_daemons) -> - Map (nb_keys, nb_alone_bakers, nb_additional_daemons)); - case - (Tag 3) - ~title:"disabled" - empty - (function Disabled -> Some () | _ -> None) - (fun () -> Disabled); - ] - -let simulate_network_to_string = function - | Scatter (x, y) -> Format.sprintf "scatter(%d,%d)" x y - | Map (x, y, z) -> Format.sprintf "map(%d,%d,%d)" x y z - | Disabled -> Format.sprintf "disabled" - module DAL = struct type t = { blocks_history : int option; producer_key : string option; fundraiser : string option; network : Network.t option; - simulate_network : network_simulation_config option; + simulate_network : Network_simulation.t option; snapshot : Snapshot_helpers.t option; bootstrap : bool option; - stake : Network.stake_repartition option; + stake : Stake_repartition.Dal.t option; bakers : string list; stake_machine_type : string list; dal_producers_slot_indices : int list; @@ -274,10 +234,10 @@ module DAL = struct (opt "producer_key" string) (opt "fundraiser" string) (opt "network" Network.encoding) - (opt "simulate_network" network_simulation_config_encoding) + (opt "simulate_network" Network_simulation.encoding) (opt "snapshot" Snapshot_helpers.encoding) (opt "bootstrap" bool) - (opt "stake" Network.stake_repartition_encoding) + (opt "stake" Stake_repartition.Dal.encoding) (dft "bakers" (list string) []) (dft "stake_machine_type" (list string) [])) (obj10 @@ -316,3 +276,106 @@ module DAL = struct (opt "tezlink" bool)))) (obj2 (opt "slot_size" int31) (opt "number_of_slots" int31))) end + +module LAYER1 = struct + module Default = struct + let maintenance_delay = 1 + + let ppx_profiling_backends = ["txt"] + + let without_dal = false + end + + type t = { + stake : Stake_repartition.Layer1.t; + network : Network.t; + snapshot : Snapshot_helpers.t; + stresstest : Stresstest.t option; + without_dal : bool; + dal_node_producers : int list option; + maintenance_delay : int; + migration_offset : int option; + ppx_profiling_verbosity : string option; + ppx_profiling_backends : string list; + signing_delay : (float * float) option; + fixed_random_seed : int option; + octez_release : string option; + } + + let encoding = + let open Data_encoding in + conv + (fun { + stake; + network; + snapshot; + stresstest; + without_dal; + dal_node_producers; + maintenance_delay; + migration_offset; + ppx_profiling_verbosity; + ppx_profiling_backends; + signing_delay; + fixed_random_seed; + octez_release; + } + -> + ( ( stake, + network, + snapshot, + stresstest, + without_dal, + dal_node_producers, + maintenance_delay, + migration_offset, + ppx_profiling_verbosity, + ppx_profiling_backends ), + (signing_delay, fixed_random_seed, octez_release) )) + (fun ( ( stake, + network, + snapshot, + stresstest, + without_dal, + dal_node_producers, + maintenance_delay, + migration_offset, + ppx_profiling_verbosity, + ppx_profiling_backends ), + (signing_delay, fixed_random_seed, octez_release) ) + -> + { + stake; + network; + snapshot; + stresstest; + without_dal; + dal_node_producers; + maintenance_delay; + migration_offset; + ppx_profiling_verbosity; + ppx_profiling_backends; + signing_delay; + fixed_random_seed; + octez_release; + }) + (merge_objs + (obj10 + (dft "stake" Stake_repartition.Layer1.encoding (Manual [1])) + (req "network" Network.encoding) + (req "snapshot" Snapshot_helpers.encoding) + (opt "stresstest" Stresstest.encoding) + (dft "without_dal" bool Default.without_dal) + (opt "dal_node_producers" (list int31)) + (dft "maintenance_delay" int31 Default.maintenance_delay) + (opt "migration_offset" int31) + (opt "ppx_profiling_verbosity" string) + (dft + "ppx_profiling_backends" + (list string) + Default.ppx_profiling_backends)) + (obj3 + (opt "signing_delay" (tup2 float float)) + (opt "fixed_random_seed" int31) + (opt "octez_release" string))) +end diff --git a/tezt/tests/cloud/scenarios_configuration.mli b/tezt/tests/cloud/scenarios_configuration.mli index 7c4bf9d2f240b86c078082a8cb5b348d09158464..925b84bf7ae312dcbde00cf2c4a0296476ac20f3 100644 --- a/tezt/tests/cloud/scenarios_configuration.mli +++ b/tezt/tests/cloud/scenarios_configuration.mli @@ -5,45 +5,16 @@ (* *) (*****************************************************************************) -(** [network_simulation_configuration] allows to configure the simulation of a - network, relying on the actual distribution of rights that will be found in - the imported data (data-dir or snapshot). It requires yes crypto to be - enabled. - The simulate option has three modes: - - scatter(x,y): selects the [x] biggest bakers found, and scatters their - baking rights, in a round robin fashion, on [y] baker daemons. This is - particularly useful to scatter the baking power across several baker - daemons, - - map(x,y,z): maps [y] keys from the biggest bakers found onto [y] baker - daemons (theses daemons are handling a single key) and scatters the - remaining [x-y] keys to [z] baker daemons. This is particularly useful to - simulate the behaviour of an actual network, - - disabled: no simulation, we rely on the configuration.stake parameter. - For example: - - scatter(10,2): [[0;2;4;6;8];[1;3;5;7;9]] - - map(10,2,1):[[0];[1];[2;3;4;5;6;7;8;9]] - - map(10,2,2):[[0];[1];[2;5;6;8];[3;5;8;9]] *) - -type network_simulation_config = - | Scatter of int * int - | Map of int * int * int - | Disabled - -val network_simulation_config_encoding : - network_simulation_config Data_encoding.t - -val simulate_network_to_string : network_simulation_config -> string - module DAL : sig type t = { blocks_history : int option; producer_key : string option; fundraiser : string option; network : Network.t option; - simulate_network : network_simulation_config option; + simulate_network : Network_simulation.t option; snapshot : Snapshot_helpers.t option; bootstrap : bool option; - stake : Network.stake_repartition option; + stake : Stake_repartition.Dal.t option; bakers : string list; stake_machine_type : string list; dal_producers_slot_indices : int list; @@ -82,3 +53,63 @@ module DAL : sig val encoding : t Data_encoding.t end + +module LAYER1 : sig + module Default : sig + val maintenance_delay : int + + val ppx_profiling_backends : string list + + val without_dal : bool + end + + (** Scenario configuration + + - [snapshot]: local path or URL of the snapshot to use for the experiment. + local path implies to [scp] the snapshot to all the vms. + + - [stake]: stake repartition between baking nodes, numbers are relatives. + + [Manual [2,1,1]] runs 3 bakers, aggregate delegates from the network, + spreading them in 3 pools representing roughly 50%, 25% and 25% of the + total stake of the network. The same stake repartition using the same + snapshot will result in the same delegate repartition. + + There is a special case using a single number instead of a list, which + gives the number of bakers to use. Delegates will be distributed as + evenly as possible between these bakers. + + [Auto] will spawn one baker per delegate, and the list of delegates will + be automatically retrieved from the provided snapshot. + + - [maintenance_delay]: number of level which will be multiplied by the + position in the list of the bakers to define the store merge delay. + We want it to be the same for two runs with same parameters (not + random) and we want it not to occur at the same time on every baker. + Default value is 1. + Use 0 for disabling delay and have all the bakers to merge their + store at the beginning of cycles. + + - [migration_offset]: offset that dictates after how many levels a protocol + upgrade will be performed via a UAU. + + - [stresstest]: See the description of [stresstest_conf] + *) + type t = { + stake : Stake_repartition.Layer1.t; + network : Network.t; + snapshot : Snapshot_helpers.t; + stresstest : Stresstest.t option; + without_dal : bool; + dal_node_producers : int list option; + maintenance_delay : int; + migration_offset : int option; + ppx_profiling_verbosity : string option; + ppx_profiling_backends : string list; + signing_delay : (float * float) option; + fixed_random_seed : int option; + octez_release : string option; + } + + val encoding : t Data_encoding.t +end diff --git a/tezt/tests/cloud/stake_repartition.ml b/tezt/tests/cloud/stake_repartition.ml new file mode 100644 index 0000000000000000000000000000000000000000..eed52a2aaabd3eebc71f9073fff0372a1874f530 --- /dev/null +++ b/tezt/tests/cloud/stake_repartition.ml @@ -0,0 +1,204 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs, *) +(* Copyright (c) 2025 Trilitech *) +(* *) +(*****************************************************************************) + +module Dal = struct + type t = + | Custom of int list + | Mimic of {network : Network.public; max_nb_bakers : int option} + + let encoding = + let open Data_encoding in + union + [ + case + (Tag 1) + ~title:"custom" + (list int31) + (function Custom l -> Some l | _ -> None) + (fun l -> Custom l); + case + (Tag 2) + ~title:"mimic" + (obj2 + (req "network" Network.public_encoding) + (opt "max_nb_bakers" int31)) + (function + | Mimic {network; max_nb_bakers} -> Some (network, max_nb_bakers) + | _ -> None) + (fun (network, max_nb_bakers) -> Mimic {network; max_nb_bakers}); + ] + + let typ = + let open Network in + let parse_public_network (net : string) : public option = + try Option.map to_public (parse net) with _ -> None + in + Clap.typ + ~name:"stake_repartition" + ~dummy:(Custom [100]) + ~parse:(fun str -> + (* If it is a list of int, then a custom repartition has been selected. *) + let int_list_regexp = Str.regexp {|\([0-9]+,\( ?\)\)*[0-9]+$|} in + if Str.string_match int_list_regexp str 0 then + Some + (Custom (str |> String.split_on_char ',' |> List.map int_of_string)) + (* Else we expect a network name, potentially followed by how many bakers should be created. *) + else + match String.split_on_char '_' str with + | [network] -> + Option.map + (fun network -> Mimic {network; max_nb_bakers = None}) + (parse_public_network network) + | [network; n_str] -> ( + try + let n = int_of_string n_str in + Option.map + (fun network -> Mimic {network; max_nb_bakers = Some n}) + (parse_public_network network) + with _ -> None) + | _ -> None) + ~show:(function + | Custom l -> + l |> List.map string_of_int |> String.concat (String.make 1 ',') + | Mimic {network; max_nb_bakers = None} -> to_string network + | Mimic {network; max_nb_bakers = Some n} -> + Format.sprintf "%s_%d" (to_string network) n) + + let parse_arg ~stake_arg ~simulation_arg = + let open Network in + match simulation_arg with + | Network_simulation.Disabled -> ( + match stake_arg with + | Custom distrib -> return distrib + | Mimic {network; max_nb_bakers} -> + let network_string = + match network with + | `Mainnet | `Ghostnet | `Rionet | `Seoulnet -> to_string network + | _ -> + failwith + (Format.sprintf + "Cannot get stake distribution for %s" + (to_string network)) + in + let endpoint = + Endpoint.make ~host:"rpc.tzkt.io" ~scheme:"https" ~port:443 () + in + let decoder json = JSON.(json |-> "cycle" |> as_int) in + let rpc = + RPC_core.( + make + GET + [ + network_string; + "chains"; + "main"; + "blocks"; + "head"; + "helpers"; + "current_level"; + ] + decoder) + in + let* response = RPC_core.call_raw endpoint rpc in + let cycle = + RPC_core.decode_raw ~origin:"Network.cycle" rpc response.body + in + let get_stake_in_ktez stake = + JSON.( + (stake |-> "frozen" |> as_int) + + (stake |-> "delegated" |> as_int)) + / 1_000_000_000 + in + let decoder json = + json |> JSON.as_list + |> List.map (fun json_account -> + let active_stake = + JSON.(json_account |-> "active_stake") + in + get_stake_in_ktez active_stake) + in + let rpc = + RPC_core.( + make + GET + [ + network_string; + "chains"; + "main"; + "blocks"; + "head"; + "context"; + "raw"; + "json"; + "cycle"; + string_of_int cycle; + "selected_stake_distribution"; + ] + decoder) + in + let* response = RPC_core.call_raw endpoint rpc in + let distribution = + RPC_core.decode_raw ~origin:"Network.cycle" rpc response.body + in + let distribution = + match max_nb_bakers with + | None -> distribution + | Some n -> Tezos_stdlib.TzList.take_n n distribution + in + return distribution) + | Scatter _ | Map _ -> ( + match stake_arg with + | Custom [] -> + (* As simulate_network and stake are mutually exclusive, only empty + stake option is allowed. *) + Lwt.return [] + | _ -> + Test.fail + "Options --simulate and --stake are mutually exclusive. We \ + cannot set baker stake while using baking power of bakers from \ + a simulated network.") +end + +module Layer1 = struct + type t = Auto | Manual of int list + + let encoding = + let open Data_encoding in + union + [ + case + (Tag 0) + ~title:"auto" + (constant "auto") + (function Auto -> Some () | _ -> None) + (fun () -> Auto); + case + (Tag 1) + ~title:"manual" + (list int31) + (function Manual d -> Some d | _ -> None) + (fun d -> Manual d); + ] + + let typ = + let parse = function + | "AUTO" | "auto" -> Some Auto + | string -> ( + try + match string |> String.split_on_char ',' with + | [n] -> Some (Manual (List.init (int_of_string n) (fun _ -> 1))) + | distribution -> + Some (Manual (List.map int_of_string distribution)) + with exn -> raise exn) + in + let show = function + | Auto -> "AUTO" + | Manual dist -> List.map string_of_int dist |> String.concat "," + in + Clap.typ ~name:"stake" ~dummy:(Manual [1]) ~parse ~show +end diff --git a/tezt/tests/cloud/stake_repartition.mli b/tezt/tests/cloud/stake_repartition.mli new file mode 100644 index 0000000000000000000000000000000000000000..13fe8845c08ee157d1e0f5a21449376c82c2a6f6 --- /dev/null +++ b/tezt/tests/cloud/stake_repartition.mli @@ -0,0 +1,28 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs, *) +(* Copyright (c) 2025 Trilitech *) +(* *) +(*****************************************************************************) + +module Dal : sig + type t = + | Custom of int list + | Mimic of {network : Network.public; max_nb_bakers : int option} + + val encoding : t Data_encoding.t + + val typ : t Clap.typ + + val parse_arg : + stake_arg:t -> simulation_arg:Network_simulation.t -> int list Lwt.t +end + +module Layer1 : sig + type t = Auto | Manual of int list + + val encoding : t Data_encoding.t + + val typ : t Clap.typ +end diff --git a/tezt/tests/cloud/stresstest.ml b/tezt/tests/cloud/stresstest.ml new file mode 100644 index 0000000000000000000000000000000000000000..e761f881830862c9087c389d8dde1cdb7e56e72c --- /dev/null +++ b/tezt/tests/cloud/stresstest.ml @@ -0,0 +1,31 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs, *) +(* Copyright (c) 2025 Trilitech *) +(* *) +(*****************************************************************************) + +type t = {tps : int; seed : int} + +let encoding = + let open Data_encoding in + conv + (fun {tps; seed} -> (tps, seed)) + (fun (tps, seed) -> {tps; seed}) + (obj2 (req "tps" int31) (req "seed" int31)) + +let to_string {tps; seed} = Format.sprintf "TPS : %d / SEED : %d" tps seed + +let typ = + Clap.typ + ~name:"stresstest" + ~dummy:{tps = 0; seed = 0} + ~parse:(fun str -> + match str |> String.split_on_char '/' with + | [n] -> + Some + {tps = int_of_string n; seed = Random.int 1073741823 (* 2^30 -1 *)} + | [n; seed] -> Some {tps = int_of_string n; seed = int_of_string seed} + | _ -> None) + ~show:to_string diff --git a/tezt/tests/cloud/stresstest.mli b/tezt/tests/cloud/stresstest.mli new file mode 100644 index 0000000000000000000000000000000000000000..a2e115d940013b2c819c0f537a3bf2a5e3720bd8 --- /dev/null +++ b/tezt/tests/cloud/stresstest.mli @@ -0,0 +1,17 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs, *) +(* Copyright (c) 2025 Trilitech *) +(* *) +(*****************************************************************************) + +(** Stresstest parameters + - [tps]: targeted number of transactions per second + - [seed]: seed used for stresstest traffic generation + *) +type t = {tps : int; seed : int} + +val encoding : t Data_encoding.t + +val typ : t Clap.typ diff --git a/tezt/tests/cloud/yes_crypto.ml b/tezt/tests/cloud/yes_crypto.ml index a12b1999f93af3bb4e60810327e292691fcfae91..4e3ba80d169e0f8d53e65f4f428a5d8d3ecfa48f 100644 --- a/tezt/tests/cloud/yes_crypto.ml +++ b/tezt/tests/cloud/yes_crypto.ml @@ -6,15 +6,13 @@ (* *) (*****************************************************************************) -open Scenarios_configuration - let yes_crypto_env = String_map.singleton Tezos_crypto.Helpers.yes_crypto_environment_variable "y" let should_enable_yes_crypto = function - | Scatter _ | Map _ -> true + | Network_simulation.Scatter _ | Map _ -> true | Disabled -> false let may_set_yes_crypto_env = function - | Scatter _ | Map _ -> (Some yes_crypto_env, true) + | Network_simulation.Scatter _ | Map _ -> (Some yes_crypto_env, true) | Disabled -> (None, false) diff --git a/tezt/tests/cloud/yes_crypto.mli b/tezt/tests/cloud/yes_crypto.mli index ee88fd6a84c450792a33cbe7313d64a2a2032f3f..6a3c99ace798df2deaf38561518b379acffc8524 100644 --- a/tezt/tests/cloud/yes_crypto.mli +++ b/tezt/tests/cloud/yes_crypto.mli @@ -10,17 +10,15 @@ which bypasses certain cryptographic confirmations in Octez nodes. *) -open Scenarios_configuration - val yes_crypto_env : string String_map.t (** [should_enable_yes_crypto config] returns a flag indicating whether "yes-crypto" mode should be enabled (for [Scatter] and [Map]) or disabled (for [Disabled]). *) -val should_enable_yes_crypto : network_simulation_config -> bool +val should_enable_yes_crypto : Network_simulation.t -> bool (** [may_set_yes_crypto_env config] is similar to [should_enable_yes_crypto], but also returns the environment variable that needs to be set to enable the yes-crypto mechanism. *) val may_set_yes_crypto_env : - network_simulation_config -> string String_map.t option * bool + Network_simulation.t -> string String_map.t option * bool