From 06ca221a3851988ed4a0d18e11461005902be66e Mon Sep 17 00:00:00 2001 From: "aurelien.foucault@lambda-coins.com" Date: Fri, 12 Dec 2025 12:20:46 +0100 Subject: [PATCH] L2Node: Add tree command to durable storage explorer shell --- etherlink/CHANGES_NODE.md | 2 + etherlink/bin_node/lib_dev/shell.ml | 12 +++++- etherlink/bin_node/lib_dev/shell.mli | 9 +++++ etherlink/bin_node/main.ml | 31 ++++++++++++++++ .../evm_sequencer.ml/EVM Node- man.out | 15 ++++++++ src/lib_layer2_shell/cli_parser.ml | 6 +++ src/lib_layer2_shell/cli_parser.mli | 2 + src/lib_layer2_shell/commands.ml | 37 +++++++++++++++++++ src/lib_layer2_shell/commands.mli | 3 ++ 9 files changed, 116 insertions(+), 1 deletion(-) diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index 42ddd1930218..9e044249d61b 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -17,6 +17,8 @@ given `PATH`, `shell ls PATH` to list the subdirectories living under `PATH`, and `shell` to start a REPL allowing users to use `ls` and `cat` interactively. See `man shell` for more information. (!20183) +- Add the `tree` command to the REPL to inspect Etherlink instance + durable storage. (!20221) ### Execution changes diff --git a/etherlink/bin_node/lib_dev/shell.ml b/etherlink/bin_node/lib_dev/shell.ml index 824f6bc55fff..5588b594ba1d 100644 --- a/etherlink/bin_node/lib_dev/shell.ml +++ b/etherlink/bin_node/lib_dev/shell.ml @@ -58,7 +58,9 @@ let ls = Commands.ls ~subkeys:Evm_state.subkeys ~inspect:Evm_state.inspect let cat = Commands.cat ~inspect:Evm_state.inspect -let commands = [Commands.Command ls; Command cat] +let tree = Commands.tree ~subkeys:Evm_state.subkeys + +let commands = [Commands.Command ls; Command cat; Command tree] let main ~config block_param = let open Lwt_result_syntax in @@ -74,3 +76,11 @@ let cat ~(config : Configuration.t) block_param pp path = let ls ~(config : Configuration.t) block_param path = run ~config block_param @@ fun _ro_ctxt tree -> Commands.run (Printer.format_printer Format.std_formatter) tree ls path + +let tree ~(config : Configuration.t) block_param path depth = + run ~config block_param @@ fun _ro_ctxt tree_state -> + Commands.run + (Printer.format_printer Format.std_formatter) + tree_state + tree + (path, depth) diff --git a/etherlink/bin_node/lib_dev/shell.mli b/etherlink/bin_node/lib_dev/shell.mli index 2b9ecc1461fa..45247c0c8a66 100644 --- a/etherlink/bin_node/lib_dev/shell.mli +++ b/etherlink/bin_node/lib_dev/shell.mli @@ -30,3 +30,12 @@ val ls : Ethereum_types.Block_parameter.extended -> string -> unit tzresult Lwt.t + +(** [tree ~config block path depth] prints the tree structure under [path] up + to [depth] levels for the specified [block]. *) +val tree : + config:Configuration.t -> + Ethereum_types.Block_parameter.extended -> + string -> + int -> + unit tzresult Lwt.t diff --git a/etherlink/bin_node/main.ml b/etherlink/bin_node/main.ml index 6334f46616ed..02b33c4ddb27 100644 --- a/etherlink/bin_node/main.ml +++ b/etherlink/bin_node/main.ml @@ -1064,6 +1064,14 @@ let desync_index_dir_arg = ~placeholder:"path|url" @@ Params.string +let depth_arg = + Tezos_clic.default_arg + ~long:"depth" + ~doc:"Depth of the tree to display." + ~placeholder:"nb" + ~default:"2" + Params.int + module Groups = struct let run = Tezos_clic.{name = "run"; title = "Run commands"} @@ -3741,6 +3749,28 @@ let shell_ls_command = in Evm_node_lib_dev.Shell.ls ~config block path) +let shell_tree_command = + let open Tezos_clic in + command + ~group:Groups.debug + ~desc:"Print the tree structure of the durable storage" + (args4 data_dir_arg config_path_arg block_param_arg depth_arg) + (prefixes ["shell"; "tree"] + @@ param + ~name:"PATH" + ~desc: + "The path of the directory whose tree structure needs to be printed" + Params.string + @@ stop) + (fun (data_dir, config_file, block, depth) path () -> + let open Lwt_result_syntax in + let* config = + Cli.create_or_read_config + ~data_dir + (config_filename ~data_dir ?config_file ()) + in + Evm_node_lib_dev.Shell.tree ~config block path depth) + let list_events_command = let open Tezos_clic in command @@ -3837,6 +3867,7 @@ let commands = shell_command; shell_cat_command; shell_ls_command; + shell_tree_command; ] let global_options = Tezos_clic.no_options 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 dc1e5c020e43..72d5b832081a 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 @@ -1101,3 +1101,18 @@ Debug commands: If set, defaults to the value of the environment variable `$EVM_NODE_CONFIG_FILE`. --block : Block parameter as per Ethereum JSON RPC specification Defaults to `latest`. + + shell tree [--data-dir ] [--config-file ] [--block ] + [--depth ] + Print the tree structure of the durable storage + : The path of the directory whose tree structure needs to be printed + --data-dir : The path to the EVM node data directory. Default is + $HOME/.octez-evm-node. + If set, defaults to the value of the environment variable `$EVM_NODE_DATA_DIR`. + --config-file : Path to a configuration file. Defaults to `config.json` inside the + data directory of the node. + If set, defaults to the value of the environment variable `$EVM_NODE_CONFIG_FILE`. + --block : Block parameter as per Ethereum JSON RPC specification + Defaults to `latest`. + --depth : Depth of the tree to display. + Defaults to `2`. diff --git a/src/lib_layer2_shell/cli_parser.ml b/src/lib_layer2_shell/cli_parser.ml index 22d868856bab..8aa4dabb4767 100644 --- a/src/lib_layer2_shell/cli_parser.ml +++ b/src/lib_layer2_shell/cli_parser.ml @@ -138,3 +138,9 @@ let required_short char = match res with | Some x -> return x | None -> fail "Expected option -%c which was missing" char + +let default_int_positive_long ?(default = 0) name = + let* v = default_long ~default:(string_of_int default) name in + match int_of_string_opt v with + | Some i when i >= 0 -> return i + | _ -> fail "Expected a positive integer for --%s" name diff --git a/src/lib_layer2_shell/cli_parser.mli b/src/lib_layer2_shell/cli_parser.mli index a98bfa9367e7..3d0b2b9b99e8 100644 --- a/src/lib_layer2_shell/cli_parser.mli +++ b/src/lib_layer2_shell/cli_parser.mli @@ -23,6 +23,8 @@ val long : string -> string option t val default_long : default:string -> string -> string t +val default_int_positive_long : ?default:int -> string -> int t + val short : char -> string option t val required_short : char -> string t diff --git a/src/lib_layer2_shell/commands.ml b/src/lib_layer2_shell/commands.ml index 5bf1c567ab0c..e4ed0dc41329 100644 --- a/src/lib_layer2_shell/commands.ml +++ b/src/lib_layer2_shell/commands.ml @@ -106,6 +106,43 @@ let cat ~inspect = return_unit | None -> return_unit) +let tree ~subkeys = + command + ~name:"tree" + ~parse: + (let open Cli_parser in + let* path = Cli.path in + let* depth = default_int_positive_long ~default:2 "depth" in + return (path, depth)) + ~run:(fun p tree (path, depth) -> + let open Lwt_syntax in + let* () = Printer.ln p "%s" (if path = "" then "." else path) in + let rec aux prefix current_path current_depth = + if current_depth <= 0 then return_unit + else + let* keys = subkeys tree current_path in + let rec loop = function + | [] -> return_unit + | "" :: ks -> loop ks + | [k] -> print_entry ~is_last:true k + | k :: ks -> + let* () = print_entry ~is_last:false k in + loop ks + and print_entry ~is_last k = + let branch = if is_last then "└── " else "├── " in + let* () = Printer.ln p "%s%s%s" prefix branch k in + let next_prefix = prefix ^ if is_last then " " else "│ " in + let next_path = + if current_path = "" then k + else String.concat "/" [current_path; k] + in + aux next_prefix next_path (current_depth - 1) + in + loop keys + in + let* () = aux "" path depth in + return_ok_unit) + type eval_read = Eval_read : 'a * ('a -> unit tzresult Lwt.t) -> eval_read let read_eval (type state) ~args (cmds : state command list) p (tree : state) = diff --git a/src/lib_layer2_shell/commands.mli b/src/lib_layer2_shell/commands.mli index 4399363c12ac..47b3c89f3cf3 100644 --- a/src/lib_layer2_shell/commands.mli +++ b/src/lib_layer2_shell/commands.mli @@ -50,3 +50,6 @@ val ls : using a specified pretty-printer (hex by default). *) val cat : inspect:('state -> string -> bytes option Lwt.t) -> (string * Pp.t, 'state) t + +val tree : + subkeys:('state -> string -> string trace Lwt.t) -> (string * int, 'state) t -- GitLab