diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml index 58d7e199cee3d7b002146c813c8561f9e6ee4e82..e8c40ab0554993e439981904f17235fa163b972a 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.ml @@ -3,7 +3,7 @@ (* Open Source License *) (* Copyright (c) 2023 Nomadic Labs *) (* Copyright (c) 2023 Marigold *) -(* Copyright (c) 2024 Functori *) +(* Copyright (c) 2024-2025 Functori *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) @@ -128,10 +128,14 @@ let decode_z_be bytes = type chain_id = Chain_id of Z.t [@@ocaml.unboxed] module Chain_id = struct + let of_string_exn s = Chain_id (Z.of_string s) + + let to_string (Chain_id s) = Z.to_string s + let encoding = Data_encoding.conv (fun (Chain_id c) -> z_to_hexa c) - (fun c -> Chain_id (Z.of_string c)) + of_string_exn Data_encoding.string let decode_le bytes = Chain_id (decode_z_le bytes) diff --git a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli index ed79c265fed2831b651784386e80bc3a04551d6f..887610972d2ade8665222d47029fa502fc432b2c 100644 --- a/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli +++ b/etherlink/bin_node/lib_dev/encodings/ethereum_types.mli @@ -3,7 +3,7 @@ (* Open Source License *) (* Copyright (c) 2023 Nomadic Labs *) (* Copyright (c) 2023 Marigold *) -(* Copyright (c) 2024 Functori *) +(* Copyright (c) 2024-2025 Functori *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) @@ -58,6 +58,13 @@ type chain_id = Chain_id of Z.t [@@unboxed] module Chain_id : sig val encoding : chain_id Data_encoding.t + (** [of_string_exn hex] transforms a string to a chain id. + It raises an exception if the string is not an number + (base 10, hexa, binary, octal ...) *) + val of_string_exn : string -> chain_id + + val to_string : chain_id -> string + val decode_le : bytes -> chain_id val decode_be : bytes -> chain_id diff --git a/etherlink/bin_node/lib_dev/kernel_config.ml b/etherlink/bin_node/lib_dev/kernel_config.ml index d5b0b7076546073bf1b9f5cf00b4cd0951b7c66b..c070a0e5902eea3892b8155c7e6fbc613ff59457 100644 --- a/etherlink/bin_node/lib_dev/kernel_config.ml +++ b/etherlink/bin_node/lib_dev/kernel_config.ml @@ -2,15 +2,25 @@ (* *) (* SPDX-License-Identifier: MIT *) (* Copyright (c) 2024 Nomadic Labs *) +(* Copyright (c) 2025 Functori *) (* *) (*****************************************************************************) -let make_instr ?(path_prefix = "/evm/") ?(convert = Fun.id) arg_opt = +let make_path = function [] -> "" | l -> "/" ^ String.concat "/" l ^ "/" + +let make_instr ?(path_prefix = ["evm"]) ?(convert = Fun.id) arg_opt = + let path_prefix = make_path path_prefix in arg_opt |> Option.map (fun (key, value) -> Installer_config.make ~key:(path_prefix ^ key) ~value:(convert value)) |> Option.to_list +let make_l2_config_instr ?(convert = Fun.id) ~l2_chain_id config = + make_instr + ~path_prefix:["evm"; "chain_configurations"; l2_chain_id] + ~convert + config + let padded_32_le_int_bytes z = String.of_bytes @@ Ethereum_types.encode_u256_le (Qty z) @@ -22,6 +32,82 @@ let parse_z_to_padded_32_le_int_bytes s = let z = Z.of_string s in padded_32_le_int_bytes z +let le_int64_bytes i = + let b = Bytes.make 8 '\000' in + Bytes.set_int64_le b 0 (Int64.of_string i) ; + String.of_bytes b + +(* When splitting the path given in argument, + their should be empty string at the start/end of + the list *) +let clean_path path = + List.fold_left + (fun acc l -> if l = "" then acc else l :: acc) + [] + (List.rev path) + +let make_l2 ~boostrap_balance ?bootstrap_accounts ?minimum_base_fee_per_gas + ?da_fee_per_byte ?sequencer_pool_address ?maximum_gas_per_transaction + ?set_account_code ?world_state_path ~l2_chain_id ~output () = + let world_state_prefix = + match world_state_path with + | None -> ["evm"; "world_state"; l2_chain_id] + | Some (_, value) -> clean_path (String.split_on_char '/' value) + in + let bootstrap_accounts = + match bootstrap_accounts with + | None -> [] + | Some bootstrap_accounts -> + let balance = padded_32_le_int_bytes boostrap_balance in + List.map + (fun address -> + make_instr + ~path_prefix:(world_state_prefix @ ["eth_accounts"; address]) + (Some ("balance", balance))) + bootstrap_accounts + |> List.flatten + in + let set_account_code = + match set_account_code with + | None -> [] + | Some set_account_codes -> + List.map + (fun (address, code) -> + make_instr + ~convert:encode_hexa + ~path_prefix:(world_state_prefix @ ["eth_accounts"; address]) + (Some ("code", code))) + set_account_codes + |> List.flatten + in + let config_instrs = + (* These configuration parameter will not be stored in the world_state of an l2 chain but are parameter for an l2 chain *) + (* To do so we put them into another path /evm/config/ *) + make_l2_config_instr + ~l2_chain_id + ~convert:parse_z_to_padded_32_le_int_bytes + minimum_base_fee_per_gas + @ make_l2_config_instr + ~l2_chain_id + ~convert:parse_z_to_padded_32_le_int_bytes + da_fee_per_byte + @ make_l2_config_instr + ~l2_chain_id + ~convert:le_int64_bytes + maximum_gas_per_transaction + @ make_l2_config_instr ~l2_chain_id world_state_path + in + let world_state_instrs = + make_instr + ~convert:(fun addr -> + let addr = Misc.normalize_addr addr in + Hex.to_bytes_exn (`Hex addr) |> String.of_bytes) + ~path_prefix:world_state_prefix + sequencer_pool_address + @ bootstrap_accounts @ set_account_code + in + Installer_config.to_file (config_instrs @ world_state_instrs) ~output + let make ~mainnet_compat ~boostrap_balance ?bootstrap_accounts ?kernel_root_hash ?chain_id ?sequencer ?delayed_bridge ?ticketer ?admin ?sequencer_governance ?kernel_governance ?kernel_security_governance ?minimum_base_fee_per_gas @@ -38,8 +124,8 @@ let make ~mainnet_compat ~boostrap_balance ?bootstrap_accounts ?kernel_root_hash List.map (fun address -> make_instr - ~path_prefix:"/evm/world_state/eth_accounts/" - (Some (address ^ "/balance", balance))) + ~path_prefix:["evm"; "world_state"; "eth_accounts"; address] + (Some ("balance", balance))) bootstrap_accounts |> List.flatten in @@ -51,16 +137,11 @@ let make ~mainnet_compat ~boostrap_balance ?bootstrap_accounts ?kernel_root_hash (fun (address, code) -> make_instr ~convert:encode_hexa - ~path_prefix:"/evm/world_state/eth_accounts/" - (Some (address ^ "/code", code))) + ~path_prefix:["evm"; "world_state"; "eth_accounts"; address] + (Some ("code", code))) set_account_codes |> List.flatten in - let le_int64_bytes i = - let b = Bytes.make 8 '\000' in - Bytes.set_int64_le b 0 (Int64.of_string i) ; - String.of_bytes b - in (* Convert a comma-separated list of decimal values in the [0; 255] range into a sequence of bytes (of type string). *) let decimal_list_to_bytes l = @@ -69,10 +150,10 @@ let make ~mainnet_compat ~boostrap_balance ?bootstrap_accounts ?kernel_root_hash |> String.of_seq in let instrs = - (if mainnet_compat then make_instr ~path_prefix:"/evm/" ticketer + (if mainnet_compat then make_instr ticketer else (* For compatibility reason for Mainnet and Ghostnet *) - make_instr ~path_prefix:"/evm/world_state/" ticketer) + make_instr ~path_prefix:["evm"; "world_state"] ticketer) @ make_instr ~convert:(fun s -> Hex.to_bytes_exn (`Hex s) |> Bytes.to_string) kernel_root_hash @@ -82,11 +163,11 @@ let make ~mainnet_compat ~boostrap_balance ?bootstrap_accounts ?kernel_root_hash @ make_instr kernel_governance @ make_instr kernel_security_governance @ make_instr - ~path_prefix:"/evm/world_state/fees/" + ~path_prefix:["evm"; "world_state"; "fees"] ~convert:parse_z_to_padded_32_le_int_bytes minimum_base_fee_per_gas @ make_instr - ~path_prefix:"/evm/world_state/fees/" + ~path_prefix:["evm"; "world_state"; "fees"] ~convert:parse_z_to_padded_32_le_int_bytes da_fee_per_byte @ make_instr ~convert:le_int64_bytes delayed_inbox_timeout @@ -101,13 +182,13 @@ let make ~mainnet_compat ~boostrap_balance ?bootstrap_accounts ?kernel_root_hash @ make_instr ~convert:le_int64_bytes max_blueprint_lookahead_in_seconds @ bootstrap_accounts @ set_account_code @ make_instr remove_whitelist - @ make_instr ~path_prefix:"/evm/feature_flags/" enable_fa_bridge - @ make_instr ~path_prefix:"/evm/feature_flags/" enable_dal + @ make_instr ~path_prefix:["evm"; "feature_flags"] enable_fa_bridge + @ make_instr ~path_prefix:["evm"; "feature_flags"] enable_dal @ make_instr - ~path_prefix:"/evm/world_state/feature_flags/" + ~path_prefix:["evm"; "world_state"; "feature_flags"] enable_fast_withdrawal @ make_instr ~convert:decimal_list_to_bytes dal_slots - @ make_instr ~path_prefix:"/evm/feature_flags/" enable_multichain + @ make_instr ~path_prefix:["evm"; "feature_flags"] enable_multichain @ make_instr ~convert:(fun s -> Ethereum_types.u16_to_bytes (int_of_string s)) max_delayed_inbox_blueprint_length diff --git a/etherlink/bin_node/lib_dev/kernel_config.mli b/etherlink/bin_node/lib_dev/kernel_config.mli index 506f31c444bf8aff8a7e4c3edde5b2901a033119..ae656139d910317e7b69c6f182408b2a9f2573b7 100644 --- a/etherlink/bin_node/lib_dev/kernel_config.mli +++ b/etherlink/bin_node/lib_dev/kernel_config.mli @@ -2,7 +2,7 @@ (* *) (* SPDX-License-Identifier: MIT *) (* Copyright (c) 2024 Nomadic Labs *) -(* Copyright (c) 2024 Functori *) +(* Copyright (c) 2024-2025 Functori *) (* *) (*****************************************************************************) @@ -41,3 +41,20 @@ val make : output:string -> unit -> unit tzresult Lwt.t + +(** [make_l2 ~boostrap_balance ?bootstrap_accounts ... ~l2_chain_id ~output ()] + generates a configuration file located at [output] for the chain [l2_chain_id], + where [bootstrap_accounts] are provisioned with [bootstrap_balance]. *) +val make_l2 : + boostrap_balance:Z.t -> + ?bootstrap_accounts:string list -> + ?minimum_base_fee_per_gas:string * string -> + ?da_fee_per_byte:string * string -> + ?sequencer_pool_address:string * string -> + ?maximum_gas_per_transaction:string * string -> + ?set_account_code:(string * string) list -> + ?world_state_path:string * string -> + l2_chain_id:string -> + output:string -> + unit -> + unit tzresult Lwt.t diff --git a/etherlink/bin_node/main.ml b/etherlink/bin_node/main.ml index 6f8e5acc42b08dbd84ebc12e151adbec2a8a98cc..a20be2d532d74c8fb2828a634244d8614d8ce9e7 100644 --- a/etherlink/bin_node/main.ml +++ b/etherlink/bin_node/main.ml @@ -1850,6 +1850,75 @@ let set_account_code = Lwt.return_ok (address, code) | _ -> failwith "Parsing error for set-code") +let make_l2_kernel_config_command = + let open Tezos_clic in + let open Lwt_result_syntax in + let open Evm_node_lib_dev_encoding.Ethereum_types in + command + ~desc: + "Produce a file containing the part of the kernel configuration \ + instructions related to a particular L2 chain." + (args9 + (config_key_arg ~name:"minimum_base_fee_per_gas" ~placeholder:"111...") + (config_key_arg ~name:"da_fee_per_byte" ~placeholder:"111...") + (config_key_arg ~name:"sequencer_pool_address" ~placeholder:"0x...") + (config_key_arg + ~name:"maximum_gas_per_transaction" + ~placeholder:"30000...") + (Tezos_clic.default_arg + ~long:"bootstrap-balance" + ~doc:"balance of the bootstrap accounts" + ~default:"9999000000000000000000" + ~placeholder:"9999000000000000000000" + @@ Tezos_clic.parameter (fun _ s -> return @@ Z.of_string s)) + bootstrap_account_arg + set_account_code + (config_key_arg + ~name:"world_state_path" + ~placeholder:"/evm/world_state/") + (Tezos_clic.arg + ~long:"l2-chain-id" + ~doc:"L2 chain id" + ~placeholder:"1" + (Tezos_clic.parameter (fun _ s -> return @@ Chain_id.of_string_exn s)))) + (prefixes ["make"; "l2"; "kernel"; "installer"; "config"] + @@ param + ~name:"kernel config file" + ~desc:"file path where the config will be written to" + Params.string + @@ stop) + (fun ( minimum_base_fee_per_gas, + da_fee_per_byte, + sequencer_pool_address, + maximum_gas_per_transaction, + boostrap_balance, + bootstrap_accounts, + set_account_code, + world_state_path, + l2_chain_id ) + output + () -> + let* l2_chain_id = + match l2_chain_id with + | None -> + failwith + "Chain_id is mandatory when trying to setup an l2 chain, use \ + --l2-chain-id to set it." + | Some l2_chain_id -> return (Chain_id.to_string l2_chain_id) + in + Evm_node_lib_dev.Kernel_config.make_l2 + ?minimum_base_fee_per_gas + ?da_fee_per_byte + ?sequencer_pool_address + ?maximum_gas_per_transaction + ~boostrap_balance + ?bootstrap_accounts + ?set_account_code + ?world_state_path + ~l2_chain_id + ~output + ()) + let make_kernel_config_command = let open Tezos_clic in let open Lwt_result_syntax in @@ -2695,6 +2764,7 @@ let commands = check_config_command; describe_config_command; make_kernel_config_command; + make_l2_kernel_config_command; patch_state_command; preemptive_kernel_download_command; debug_print_store_schemas_command; 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 e7d862c58de570429617a7efd41eaa36e97a8600..5b89e52759f3df3137dec447f7d8476ed2e0c25b 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 @@ -510,6 +510,27 @@ Miscellaneous commands: --max-delayed-inbox-blueprint-length <1000>: value for max_delayed_inbox_blueprint_length in the installer config --enable-fast-withdrawal: enable flag enable_fast_withdrawal in the installer config + octez-evm-node make l2 kernel installer config [--minimum-base-fee-per-gas <111...>] + [--da-fee-per-byte <111...>] + [--sequencer-pool-address <0x...>] + [--maximum-gas-per-transaction <30000...>] + [--bootstrap-balance <9999000000000000000000>] + [--bootstrap-account <0x...>] + [--set-code <0x...,0x....>] + [--world-state-path >] + [--l2-chain-id <1>] + Produce a file containing the part of the kernel configuration instructions related to a particular L2 chain. + : file path where the config will be written to + --minimum-base-fee-per-gas <111...>: value for minimum_base_fee_per_gas in the installer config + --da-fee-per-byte <111...>: value for da_fee_per_byte in the installer config + --sequencer-pool-address <0x...>: value for sequencer_pool_address in the installer config + --maximum-gas-per-transaction <30000...>: value for maximum_gas_per_transaction in the installer config + --bootstrap-balance <9999000000000000000000>: balance of the bootstrap accounts + --bootstrap-account <0x...>: add a bootstrap account in the installer config. + --set-code <0x...,0x....>: add code to an account in the installer config. + --world-state-path >: value for world_state_path in the installer config + --l2-chain-id <1>: L2 chain id + octez-evm-node patch state at with [--data-dir ] [--block-number ] [-f --force] Patches the state with an arbitrary value. This is an unsafe command, it should be used for debugging only. Patched state is persisted and you need to use the command `reset` to revert the changes.