From 2dc88530a50c2be3320d412a9a5e4f348eee8101 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 3 Dec 2024 21:09:01 +0100 Subject: [PATCH 01/11] EVM node/Config: experimental feature option for Dream HTTP server --- etherlink/bin_node/config/configuration.ml | 21 ++++++++++++++++++++- etherlink/bin_node/config/configuration.mli | 6 ++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/etherlink/bin_node/config/configuration.ml b/etherlink/bin_node/config/configuration.ml index a5c166a8001e..b6ec612e9576 100644 --- a/etherlink/bin_node/config/configuration.ml +++ b/etherlink/bin_node/config/configuration.ml @@ -40,6 +40,8 @@ type garbage_collector = { history_to_keep_in_seconds : int; } +type rpc_server = Resto | Dream + type experimental_features = { drop_duplicate_on_injection : bool; enable_send_raw_transaction : bool; @@ -47,6 +49,7 @@ type experimental_features = { block_storage_sqlite3 : bool; overwrite_simulation_tick_limit : bool; garbage_collector : garbage_collector option; + rpc_server : rpc_server; enable_websocket : bool; } @@ -139,6 +142,7 @@ let default_experimental_features = block_storage_sqlite3 = false; overwrite_simulation_tick_limit = false; garbage_collector = None; + rpc_server = Resto; enable_websocket = false; } @@ -686,6 +690,10 @@ let garbage_collector_encoding = collected, e.g. 86_400 will keep 1 day of history." int31)) +let rpc_server_encoding = + let open Data_encoding in + string_enum [("resto", Resto); ("dream", Dream)] + let experimental_features_encoding = let open Data_encoding in conv @@ -696,6 +704,7 @@ let experimental_features_encoding = block_storage_sqlite3; overwrite_simulation_tick_limit; garbage_collector; + rpc_server; enable_websocket; } -> ( drop_duplicate_on_injection, @@ -705,6 +714,7 @@ let experimental_features_encoding = overwrite_simulation_tick_limit, garbage_collector, None, + rpc_server, enable_websocket )) (fun ( drop_duplicate_on_injection, enable_send_raw_transaction, @@ -713,6 +723,7 @@ let experimental_features_encoding = overwrite_simulation_tick_limit, garbage_collector, _next_wasm_runtime, + rpc_server, enable_websocket ) -> { drop_duplicate_on_injection; @@ -721,9 +732,10 @@ let experimental_features_encoding = block_storage_sqlite3; overwrite_simulation_tick_limit; garbage_collector; + rpc_server; enable_websocket; }) - (obj8 + (obj9 (dft ~description: "Request the rollup node to filter messages it has already \ @@ -769,6 +781,13 @@ let experimental_features_encoding = to replace the Smart Rollup’s Fast Exec runtime. DEPRECATED: You \ should remove this option from your configuration file." bool) + (dft + "rpc_server" + ~description: + "Choose the RPC server implementation, \'dream\' or \'resto\', the \ + latter being the default one." + rpc_server_encoding + default_experimental_features.rpc_server) (dft "enable_websocket" ~description:"Enable or disable the experimental websocket server" diff --git a/etherlink/bin_node/config/configuration.mli b/etherlink/bin_node/config/configuration.mli index cc581d5646b7..52f9e21d10b5 100644 --- a/etherlink/bin_node/config/configuration.mli +++ b/etherlink/bin_node/config/configuration.mli @@ -59,6 +59,11 @@ type garbage_collector = { history_to_keep_in_seconds : int; } +(** RPC server implementation. *) +type rpc_server = + | Resto (** Resto/Cohttp (default) *) + | Dream (** Dream/httpun *) + (** Configuration settings for experimental features, with no backward compatibility guarantees. *) type experimental_features = { @@ -68,6 +73,7 @@ type experimental_features = { block_storage_sqlite3 : bool; overwrite_simulation_tick_limit : bool; garbage_collector : garbage_collector option; + rpc_server : rpc_server; enable_websocket : bool; } -- GitLab From d97da8cd6e811efb5c4de1166110754d6b1b0c35 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Wed, 4 Dec 2024 15:39:45 +0100 Subject: [PATCH 02/11] Tests: regression for config --- .../expected/evm_sequencer.ml/Alpha- Configuration RPC.out | 3 +++ .../expected/evm_sequencer.ml/EVM Node- describe config.out | 1 + 2 files changed, 4 insertions(+) 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 0cb9ee7f3c28..6d8dc266e546 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 @@ -16,6 +16,7 @@ "node_transaction_validation": true, "block_storage_sqlite3": true, "overwrite_simulation_tick_limit": false, + "rpc_server": "resto", "enable_websocket": false }, "proxy": { @@ -66,6 +67,7 @@ "node_transaction_validation": true, "block_storage_sqlite3": true, "overwrite_simulation_tick_limit": false, + "rpc_server": "resto", "enable_websocket": false }, "proxy": { @@ -109,6 +111,7 @@ "node_transaction_validation": true, "block_storage_sqlite3": true, "overwrite_simulation_tick_limit": false, + "rpc_server": "resto", "enable_websocket": false }, "proxy": { 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 0780a9e04a48..bc72b1a6727b 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 @@ -148,6 +148,7 @@ /* Enable or disable the experimental WASM runtime that is expected to replace the Smart Rollup’s Fast Exec runtime. DEPRECATED: You should remove this option from your configuration file. */, + "rpc_server"?: "dream" | "resto", "enable_websocket"?: boolean /* Enable or disable the experimental websocket server */ }, -- GitLab From c2d293f645f892327c43c3cdc665c77b787e9a8c Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Wed, 6 Nov 2024 21:22:02 +0100 Subject: [PATCH 03/11] EVM node: wrapper around Dream to define routes based on Resto services --- etherlink/bin_node/lib_dev/router.ml | 233 ++++++++++++++++++++++++++ etherlink/bin_node/lib_dev/router.mli | 42 +++++ resto/src/resto.ml | 4 + resto/src/resto.mli | 4 + 4 files changed, 283 insertions(+) create mode 100644 etherlink/bin_node/lib_dev/router.ml create mode 100644 etherlink/bin_node/lib_dev/router.mli diff --git a/etherlink/bin_node/lib_dev/router.ml b/etherlink/bin_node/lib_dev/router.ml new file mode 100644 index 000000000000..3201fbdb76d9 --- /dev/null +++ b/etherlink/bin_node/lib_dev/router.ml @@ -0,0 +1,233 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Functori *) +(* Copyright (c) 2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +open Tezos_rpc + +type media = [`Json | `Octets] + +let default_media = `Json + +let application_octet_stream = "application/octet-stream" + +type accept_media = {media : [media | `Any]; q : float} + +let parse_media_header s = + match String.split_on_char ';' s with + | [] -> None + | accept :: rest -> ( + try + let media = + match accept |> String.trim |> String.lowercase_ascii with + | "application/json" -> `Json + | "application/octet-stream" -> `Octets + | "application/*" | "*/*" | "*" -> `Any + | _ -> raise Not_found + in + let rec prio r = + match r with + | [] -> 1.0 + | q :: r -> ( + try Scanf.sscanf q " q = %f " (fun f -> f) with _ -> prio r) + in + Some {media; q = prio rest} + with Not_found -> None) + +let media header_name request = + let medias = Dream.headers request header_name in + let medias = + List.fold_left + (fun acc h -> List.rev_append (String.split_on_char ',' h) acc) + [] + medias + |> List.rev + in + let parsed = + List.filter_map parse_media_header medias + |> List.stable_sort (fun a1 a2 -> Compare.Float.compare a2.q a1.q) + in + match parsed with + | [] | {media = `Any; _} :: _ -> default_media + | {media = #media as m; _} :: _ -> m + +let accept_media = media "accept" + +let content_media = media "content-type" + +let encode media encoding = + (* We use the functions from Tezos_rpc_http to remain compatible with the + Resto encodings. *) + match media with + | `Json -> Tezos_rpc_http.Media_type.json.construct encoding + | `Octets -> Tezos_rpc_http.Media_type.octet_stream.construct encoding + +let decode media encoding = + match media with + | `Json -> + fun s -> + Ezjsonm.value_from_string s |> Data_encoding.Json.destruct encoding + | `Octets -> + Data_encoding.Binary.of_string_exn (Data_encoding.dynamic_size encoding) + +let content_header = function + | `Json -> Dream.application_json + | `Octets -> application_octet_stream + +let respond ?status ?code ?headers media v = + let open Lwt_syntax in + let* response = Dream.respond ?status ?code ?headers v in + Dream.set_header response "Content-type" (content_header media) ; + return response + +let get_params : + type params. + Dream.request -> (unit, params) Path.t -> (params, string) result = + fun request p -> + let open Result_syntax in + let rec extract : + type param. (unit, param) Resto.Internal.path -> (param, string) result = + function + | Root -> return_unit + | Static (path, _s) -> extract path + | Dynamic (path, arg) -> + let* rest = extract path in + let* param_str = + try Dream.param request arg.descr.name |> return with + | Failure s -> fail s + | exn -> fail (Printexc.to_string exn) + in + let* param = arg.destruct param_str in + return (rest, param) + | DynamicTail (_, _) -> + fail "Dynamic tail services not implemented for Dream router" + in + extract (Resto.Internal.to_path p) + +let get_queries request query = + try Ok (Query.parse query (Dream.all_queries request)) with + | Resto.Query.Invalid s -> Error s + | exn -> Error (Printexc.to_string exn) + +let dream_path (path : _ Path.t) : string = + let to_segments : type pr p. (pr, p) Resto.Internal.path -> string list = + fun path -> + let rec flatten_rev : type pr p. (pr, p) Resto.Internal.path -> string list + = function + | Root -> [] + | Static (p, s) -> s :: flatten_rev p + | Dynamic (p, arg) -> Printf.sprintf ":%s" arg.descr.name :: flatten_rev p + | DynamicTail (_, _) -> + invalid_arg "Dynamic tail services not implemented for Dream router" + in + List.rev @@ flatten_rev path + in + "/" ^ String.concat "/" (to_segments (Resto.Internal.to_path path)) + +let bad_request media msg = + respond ~status:`Bad_Request media (encode media Data_encoding.string msg) + +let make_gen_route : + type params query input. + (_, _, params, query, input, _) Service.t -> + (Dream.request -> + params:params -> + query:query -> + input -> + Dream.response Lwt.t) -> + Dream.route = + fun service handler -> + let f = + match Service.meth service with + | `PUT -> Dream.put + | `GET -> Dream.get + | `DELETE -> Dream.delete + | `POST -> Dream.post + | `PATCH -> Dream.patch + in + let input_encoding = Service.input_encoding service in + let path = dream_path (Service.path service) in + f path @@ fun request -> + let open Lwt_syntax in + let output_media = accept_media request in + match get_queries request (Service.query service) with + | Error e -> bad_request output_media e + | Ok query -> ( + match get_params request (Service.path service) with + | Error e -> bad_request output_media e + | Ok params -> ( + match input_encoding with + | No_input -> handler request ~params ~query () + | Input input_encoding -> + let* body = Dream.body request in + let media = content_media request in + let input = decode media input_encoding body in + handler request ~params ~query input)) + +let make_route service handler = + make_gen_route service @@ fun request ~params ~query input -> + let open Lwt_syntax in + let output_encoding = Service.output_encoding service in + let* output = handler ~params ~query input in + let media = accept_media request in + respond media (encode media output_encoding output) + +let make_tz_route service handler = + make_gen_route service @@ fun request ~params ~query input -> + let open Lwt_syntax in + let* output = handler ~params ~query input in + let media = accept_media request in + match output with + | Ok output -> + let output_encoding = Service.output_encoding service in + respond media (encode media output_encoding output) + | Error e -> + respond + ~status:`Internal_Server_Error + media + (encode media trace_encoding e) + +let make_opt_tz_route service handler = + make_gen_route service @@ fun request ~params ~query input -> + let open Lwt_syntax in + let* output = handler ~params ~query input in + let media = accept_media request in + match output with + | Ok (Some output) -> + let output_encoding = Service.output_encoding service in + respond media (encode media output_encoding output) + | Ok None -> + respond + ~status:`Not_Found + media + (encode + media + Data_encoding.string + (Dream.target request ^ " not found on server")) + | Error e -> + respond + ~status:`Internal_Server_Error + media + (encode media trace_encoding e) + +let make_stream_route service handler = + make_gen_route service @@ fun request ~params ~query input -> + let open Lwt_syntax in + let output_encoding = Service.output_encoding service in + let media = accept_media request in + Dream.stream ~headers:[("Content-Type", content_header media)] + @@ fun response_stream -> + let* stream, shutdown = handler ~params ~query input in + let* () = + Lwt_stream.iter_s + (fun output -> + let chunk = encode media output_encoding output in + let* () = Dream.write response_stream chunk in + Dream.flush response_stream) + stream + in + shutdown () ; + return_unit diff --git a/etherlink/bin_node/lib_dev/router.mli b/etherlink/bin_node/lib_dev/router.mli new file mode 100644 index 000000000000..39f2232edf91 --- /dev/null +++ b/etherlink/bin_node/lib_dev/router.mli @@ -0,0 +1,42 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Functori *) +(* Copyright (c) 2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +(** {1 Helper functions to build {!Dream} routes from {!Resto} services.} *) + +(** [make_route service handler] builds a route from a handler that + returns an output. *) +val make_route : + ([< Resto.meth], unit, 'params, 'query, 'input, 'output) Tezos_rpc.Service.t -> + (params:'params -> query:'query -> 'input -> 'output Lwt.t) -> + Dream.route + +(** [make_tz_route service handler] builds a route from a handler that + returns an output or an error. *) +val make_tz_route : + ([< Resto.meth], unit, 'params, 'query, 'input, 'output) Tezos_rpc.Service.t -> + (params:'params -> query:'query -> 'input -> 'output tzresult Lwt.t) -> + Dream.route + +(** [make_opt_tz_route service handler] builds a route from a handler that + returns an optional output or an error. If [handler] returns [None] the + server answers with a 404 Not_Found response. *) +val make_opt_tz_route : + ([< Resto.meth], unit, 'params, 'query, 'input, 'output) Tezos_rpc.Service.t -> + (params:'params -> query:'query -> 'input -> 'output option tzresult Lwt.t) -> + Dream.route + +(** [make_stream_route service handler] builds a route which streams the + response from a handler that constructs an {!Lwt_stream.t}. The output + stream is streamed as chunks in the response body.. *) +val make_stream_route : + ([< Resto.meth], unit, 'params, 'query, 'input, 'output) Tezos_rpc.Service.t -> + (params:'params -> + query:'query -> + 'input -> + ('output Lwt_stream.t * (unit -> unit)) Lwt.t) -> + Dream.route diff --git a/resto/src/resto.ml b/resto/src/resto.ml index 3f3937e741a2..81f90c4c4fa9 100644 --- a/resto/src/resto.ml +++ b/resto/src/resto.ml @@ -760,6 +760,10 @@ module MakeService (Encoding : ENCODING) = struct let query : type pr p i q o e. (_, pr, p, q, i, o, e) service -> q Query.t = fun {types; _} -> types.query + let path : type pr p i q o e. (_, pr, p, q, i, o, e) service -> (pr, p) Path.t + = + fun {path; _} -> path + let input_encoding : type pr p i q o e. (_, pr, p, q, i, o, e) service -> i input = fun {types; _} -> types.input diff --git a/resto/src/resto.mli b/resto/src/resto.mli index 8b1aaff3f2ba..58962620c2a2 100644 --- a/resto/src/resto.mli +++ b/resto/src/resto.mli @@ -564,6 +564,10 @@ module MakeService (Encoding : ENCODING) : sig ('meth, 'prefix, 'params, 'query, 'input, 'output, 'error) service -> 'query Query.t + val path : + ('meth, 'prefix, 'params, 'query, 'input, 'output, 'error) service -> + ('prefix, 'params) Path.t + type _ input = | No_input : unit input | Input : 'input Encoding.t -> 'input input -- GitLab From 66144905662a1d022c3e0c313840b927a7922d04 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 5 Dec 2024 16:35:52 +0100 Subject: [PATCH 04/11] EVM node: RPC directory which supports both Resto and Dream --- etherlink/bin_node/lib_dev/evm_directory.ml | 120 +++++++++++++++ etherlink/bin_node/lib_dev/evm_directory.mli | 154 +++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 etherlink/bin_node/lib_dev/evm_directory.ml create mode 100644 etherlink/bin_node/lib_dev/evm_directory.mli diff --git a/etherlink/bin_node/lib_dev/evm_directory.ml b/etherlink/bin_node/lib_dev/evm_directory.ml new file mode 100644 index 000000000000..1238dc1ad404 --- /dev/null +++ b/etherlink/bin_node/lib_dev/evm_directory.ml @@ -0,0 +1,120 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Functori *) +(* Copyright (c) 2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +type t = Resto of unit Tezos_rpc.Directory.t | Dream of Dream.route list + +let empty = function + | Configuration.Resto -> Resto Tezos_rpc.Directory.empty + | Configuration.Dream -> Dream [] + +let register dir service handler = + match dir with + | Resto dir -> Resto (Tezos_rpc.Directory.register dir service handler) + | Dream routes -> + let route = + Router.make_tz_route service (fun ~params ~query input -> + handler params query input) + in + Dream (route :: routes) + +let opt_register dir service handler = + match dir with + | Resto dir -> Resto (Tezos_rpc.Directory.opt_register dir service handler) + | Dream routes -> + let route = + Router.make_opt_tz_route service (fun ~params ~query input -> + handler params query input) + in + Dream (route :: routes) + +let lwt_register dir service handler = + match dir with + | Resto dir -> Resto (Tezos_rpc.Directory.lwt_register dir service handler) + | Dream routes -> + let route = + Router.make_route service (fun ~params ~query input -> + handler params query input) + in + Dream (route :: routes) + +let streamed_register dir service handler = + let open Lwt_syntax in + match dir with + | Resto dir -> + let dir = + Tezos_rpc.Directory.gen_register dir service (fun params query input -> + let* stream, shutdown = handler params query input in + let next () = Lwt_stream.get stream in + Tezos_rpc.Answer.return_stream {next; shutdown}) + in + Resto dir + | Dream routes -> + let route = + Router.make_stream_route service (fun ~params ~query input -> + handler params query input) + in + Dream (route :: routes) + +module Curry = struct + type (_, _, _, _, _, _) conv = + | Z : (unit, 'g, 'g, unit, 'f, 'f) conv + | S : + ('t, 'g, 'b * 's, 'rt, 'f, 'r) conv + -> ('t * 'b, 'g, 's, 'a * 'rt, 'a -> 'f, 'r) conv + + let reverse : type a c d e f. (a, c, unit, d, e, f) conv -> a -> c = + fun c v -> + let rec reverse : type a c d e f g. (a, c, d, e, f, g) conv -> a -> d -> c = + fun c v acc -> + match (c, v) with Z, _ -> acc | S c, (v, x) -> reverse c v (x, acc) + in + reverse c v () + + let rec curry : type a b c d e f. (a, b, c, d, e, f) conv -> e -> d -> f = + fun c f -> + match c with Z -> fun () -> f | S c -> fun (v, x) -> curry c (f v) x + + let curry c f = + let f = curry c f in + fun x -> f (reverse c x) +end + +let register0 dir service handler = register dir service Curry.(curry Z handler) + +let register1 dir service handler = + register dir service Curry.(curry (S Z) handler) + +let register2 dir service handler = + register dir service Curry.(curry (S (S Z)) handler) + +let opt_register0 dir service handler = + opt_register dir service Curry.(curry Z handler) + +let opt_register1 dir service handler = + opt_register dir service Curry.(curry (S Z) handler) + +let opt_register2 dir service handler = + opt_register dir service Curry.(curry (S (S Z)) handler) + +let lwt_register0 dir service handler = + lwt_register dir service Curry.(curry Z handler) + +let lwt_register1 dir service handler = + lwt_register dir service Curry.(curry (S Z) handler) + +let lwt_register2 dir service handler = + lwt_register dir service Curry.(curry (S (S Z)) handler) + +let streamed_register0 dir service handler = + streamed_register dir service Curry.(curry Z handler) + +let streamed_register1 dir service handler = + streamed_register dir service Curry.(curry (S Z) handler) + +let streamed_register2 dir service handler = + streamed_register dir service Curry.(curry (S (S Z)) handler) diff --git a/etherlink/bin_node/lib_dev/evm_directory.mli b/etherlink/bin_node/lib_dev/evm_directory.mli new file mode 100644 index 000000000000..6d3877801e23 --- /dev/null +++ b/etherlink/bin_node/lib_dev/evm_directory.mli @@ -0,0 +1,154 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Functori *) +(* Copyright (c) 2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +(** {1 Directories depending on backends} *) + +(** The type of RPC directory for EVM node depending on the chosen RPC server + backend. *) +type t = private + | Resto of unit Tezos_rpc.Directory.t (** A Resto directory *) + | Dream of Dream.route trace (** A list of Dream routes *) + +(** An empty directory depending on the RPC server backend. *) +val empty : Configuration.rpc_server -> t + +(** {1 Registering services} *) + +(** {2 Generic functions} *) + +(** Register a new service with it's handler. *) +val register : + t -> + ([< Resto.meth], unit, 'params, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('params -> 'query -> 'input -> 'output tzresult Lwt.t) -> + t + +(** Register a new service with it's handler. The server answers with 404 + Not_Found if the handler returns [None]. *) +val opt_register : + t -> + ([< Resto.meth], unit, 'params, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('params -> 'query -> 'input -> 'output option tzresult Lwt.t) -> + t + +(** Register a new service with it's handler. *) +val lwt_register : + t -> + ([< Resto.meth], unit, 'params, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('params -> 'query -> 'input -> 'output Lwt.t) -> + t + +(** Register a new streamed service. The handler should produce output elements + in a stream. *) +val streamed_register : + t -> + ([< Resto.meth], unit, 'params, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('params -> 'query -> 'input -> ('output Lwt_stream.t * (unit -> unit)) Lwt.t) -> + t + +(** {2 Curried functions with respect to service parameters} *) + +val register0 : + t -> + ([< Resto.meth], unit, unit, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('query -> 'input -> 'output tzresult Lwt.t) -> + t + +val register1 : + t -> + ([< Resto.meth], unit, unit * 'a, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('a -> 'query -> 'input -> 'output tzresult Lwt.t) -> + t + +val register2 : + t -> + ( [< Resto.meth], + unit, + (unit * 'a) * 'b, + 'query, + 'input, + 'output ) + Tezos_rpc.Service.t -> + ('a -> 'b -> 'query -> 'input -> 'output tzresult Lwt.t) -> + t + +val opt_register0 : + t -> + ([< Resto.meth], unit, unit, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('query -> 'input -> 'output option tzresult Lwt.t) -> + t + +val opt_register1 : + t -> + ([< Resto.meth], unit, unit * 'a, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('a -> 'query -> 'input -> 'output option tzresult Lwt.t) -> + t + +val opt_register2 : + t -> + ( [< Resto.meth], + unit, + (unit * 'a) * 'b, + 'query, + 'input, + 'output ) + Tezos_rpc.Service.t -> + ('a -> 'b -> 'query -> 'input -> 'output option tzresult Lwt.t) -> + t + +val lwt_register0 : + t -> + ([< Resto.meth], unit, unit, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('query -> 'input -> 'output Lwt.t) -> + t + +val lwt_register1 : + t -> + ([< Resto.meth], unit, unit * 'a, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('a -> 'query -> 'input -> 'output Lwt.t) -> + t + +val lwt_register2 : + t -> + ( [< Resto.meth], + unit, + (unit * 'a) * 'b, + 'query, + 'input, + 'output ) + Tezos_rpc.Service.t -> + ('a -> 'b -> 'query -> 'input -> 'output Lwt.t) -> + t + +val streamed_register0 : + t -> + ([< Resto.meth], unit, unit, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('query -> 'input -> ('output Lwt_stream.t * (unit -> unit)) Lwt.t) -> + t + +val streamed_register1 : + t -> + ([< Resto.meth], unit, unit * 'a, 'query, 'input, 'output) Tezos_rpc.Service.t -> + ('a -> 'query -> 'input -> ('output Lwt_stream.t * (unit -> unit)) Lwt.t) -> + t + +val streamed_register2 : + t -> + ( [< Resto.meth], + unit, + (unit * 'a) * 'b, + 'query, + 'input, + 'output ) + Tezos_rpc.Service.t -> + ('a -> + 'b -> + 'query -> + 'input -> + ('output Lwt_stream.t * (unit -> unit)) Lwt.t) -> + t -- GitLab From da124f8c9969171de08a8bfd4a124f21d62dd449 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 5 Dec 2024 17:04:19 +0100 Subject: [PATCH 05/11] EVM node: RPC server which uses Dream/httpun instead of Resto/Cohttp --- etherlink/bin_node/lib_dev/evm_services.ml | 19 +- etherlink/bin_node/lib_dev/evm_services.mli | 6 +- etherlink/bin_node/lib_dev/rpc_server.ml | 97 ++++++---- etherlink/bin_node/lib_dev/services.ml | 186 ++++++++++---------- 4 files changed, 168 insertions(+), 140 deletions(-) diff --git a/etherlink/bin_node/lib_dev/evm_services.ml b/etherlink/bin_node/lib_dev/evm_services.ml index 2032942a7ac3..5e9476a94d43 100644 --- a/etherlink/bin_node/lib_dev/evm_services.ml +++ b/etherlink/bin_node/lib_dev/evm_services.ml @@ -16,7 +16,7 @@ let get_smart_rollup_address_service = ~output:Tezos_crypto.Hashed.Smart_rollup_address.encoding Path.(evm_services_root / "smart_rollup_address") -let get_time_between_blocks = +let get_time_between_blocks_service = Service.get_service ~description:"Get the maximum time between two blocks" ~query:Query.empty @@ -80,7 +80,7 @@ let create_blueprint_stream get_next_blueprint_number find_blueprint from_level "Something went wrong when trying to fetch a blueprint") else Lwt_stream.get stream in - Tezos_rpc.Answer.return_stream {next; shutdown} + return (Lwt_stream.from next, shutdown) let create_blueprint_watcher_service get_next_blueprint_number find_blueprint from_level = @@ -101,17 +101,17 @@ let create_broadcast_service get_next_blueprint_number find_blueprint from_level (fun b -> Lwt_syntax.return_some @@ Broadcast.Blueprint b) let register_get_smart_rollup_address_service smart_rollup_address dir = - Directory.register0 dir get_smart_rollup_address_service (fun () () -> + Evm_directory.register0 dir get_smart_rollup_address_service (fun () () -> let open Lwt_syntax in return_ok smart_rollup_address) let register_get_time_between_block_service time_between_block dir = - Directory.register0 dir get_time_between_blocks (fun () () -> + Evm_directory.register0 dir get_time_between_blocks_service (fun () () -> let open Lwt_result_syntax in return time_between_block) let register_get_blueprint_service find_blueprint dir = - Directory.opt_register1 dir get_blueprint_service (fun level () () -> + Evm_directory.opt_register1 dir get_blueprint_service (fun level () () -> let open Lwt_result_syntax in let number = Ethereum_types.Qty (Z.of_int64 level) in let* blueprint = find_blueprint number in @@ -119,14 +119,17 @@ let register_get_blueprint_service find_blueprint dir = let register_blueprint_watcher_service find_blueprint get_next_blueprint_number dir = - Directory.gen_register0 dir blueprint_watcher_service (fun level () -> + Evm_directory.streamed_register0 + dir + blueprint_watcher_service + (fun level () -> create_blueprint_watcher_service get_next_blueprint_number find_blueprint level) let register_broadcast_service find_blueprint get_next_blueprint_number dir = - Directory.gen_register0 dir message_watcher_service (fun level () -> + Evm_directory.streamed_register0 dir message_watcher_service (fun level () -> create_broadcast_service get_next_blueprint_number find_blueprint level) let register get_next_blueprint_number find_blueprint smart_rollup_address @@ -152,7 +155,7 @@ let get_time_between_blocks ?fallback ~evm_node_endpoint () = Tezos_rpc_http_client_unix.RPC_client_unix.call_service [Media_type.octet_stream] ~base:evm_node_endpoint - get_time_between_blocks + get_time_between_blocks_service () () () diff --git a/etherlink/bin_node/lib_dev/evm_services.mli b/etherlink/bin_node/lib_dev/evm_services.mli index e774b0a77c6b..70800b4a9fc4 100644 --- a/etherlink/bin_node/lib_dev/evm_services.mli +++ b/etherlink/bin_node/lib_dev/evm_services.mli @@ -5,8 +5,6 @@ (* *) (*****************************************************************************) -open Tezos_rpc - val get_smart_rollup_address : evm_node_endpoint:Uri.t -> Tezos_crypto.Hashed.Smart_rollup_address.t tzresult Lwt.t @@ -28,8 +26,8 @@ val register : (Ethereum_types.quantity -> Blueprint_types.with_events option tzresult Lwt.t) -> Tezos_crypto.Hashed.Smart_rollup_address.t -> Configuration.time_between_blocks -> - unit Directory.t -> - unit Directory.t + Evm_directory.t -> + Evm_directory.t val monitor_blueprints : evm_node_endpoint:Uri.t -> diff --git a/etherlink/bin_node/lib_dev/rpc_server.ml b/etherlink/bin_node/lib_dev/rpc_server.ml index d70839c65413..cce0306dbcbc 100644 --- a/etherlink/bin_node/lib_dev/rpc_server.ml +++ b/etherlink/bin_node/lib_dev/rpc_server.ml @@ -44,46 +44,71 @@ let callback server dir = dir callback_log -let start_server rpc directory = - let open Lwt_result_syntax in - let open Tezos_rpc_http_server in - let Configuration. - {port; addr; cors_origins; cors_headers; max_active_connections; _} = - rpc - in +module Resto = struct + let start_server rpc directory = + let open Lwt_result_syntax in + let open Tezos_rpc_http_server in + let Configuration. + {port; addr; cors_origins; cors_headers; max_active_connections; _} = + rpc + in - let p2p_addr = P2p_addr.of_string_exn addr in - let host = Ipaddr.V6.to_string p2p_addr in - let node = `TCP (`Port port) in - let acl = RPC_server.Acl.allow_all in - let cors = - Resto_cohttp.Cors. - {allowed_headers = cors_headers; allowed_origins = cors_origins} - in - let server = - RPC_server.init_server - ~acl - ~cors - ~media_types:Supported_media_types.all - directory - in + let p2p_addr = P2p_addr.of_string_exn addr in + let host = Ipaddr.V6.to_string p2p_addr in + let node = `TCP (`Port port) in + let acl = RPC_server.Acl.allow_all in + let cors = + Resto_cohttp.Cors. + {allowed_headers = cors_headers; allowed_origins = cors_origins} + in + let server = + RPC_server.init_server + ~acl + ~cors + ~media_types:Supported_media_types.all + directory + in - let*! () = - RPC_server.launch - ~max_active_connections - ~host - server - ~callback:(callback server directory) - node - in + let*! () = + RPC_server.launch + ~max_active_connections + ~host + server + ~callback:(callback server directory) + node + in - let finalizer () = - let open Lwt_syntax in - let* () = Tezos_rpc_http_server.RPC_server.shutdown server in - return_unit - in + let finalizer () = + let open Lwt_syntax in + let* () = Tezos_rpc_http_server.RPC_server.shutdown server in + return_unit + in - return finalizer + return finalizer +end + +module Dream = struct + let start_server rpc routes = + let open Lwt_result_syntax in + let Configuration.{port; addr; cors_origins = _; cors_headers = _; _} = + rpc + in + let stop, resolve_stop = Lwt.wait () in + let shutdown () = + Lwt.wakeup_later resolve_stop () ; + Lwt.return_unit + in + Lwt.dont_wait + (fun () -> + routes |> Dream.router |> Dream.serve ~interface:addr ~port ~stop) + (fun exn -> + Format.eprintf "Dream server error: %s@." (Printexc.to_string exn)) ; + return shutdown +end + +let start_server rpc = function + | Evm_directory.Resto dir -> Resto.start_server rpc dir + | Evm_directory.Dream routes -> Dream.start_server rpc routes let start_public_server ?delegate_health_check_to ?evm_services (config : Configuration.t) ctxt = diff --git a/etherlink/bin_node/lib_dev/services.ml b/etherlink/bin_node/lib_dev/services.ml index 851718525750..05567ab7d3fa 100644 --- a/etherlink/bin_node/lib_dev/services.ml +++ b/etherlink/bin_node/lib_dev/services.ml @@ -72,83 +72,77 @@ let client_version = Stdlib.Sys.os_type Stdlib.Sys.ocaml_version +let configuration_handler config = + let open Configuration in + (* Hide some parts of the configuration. *) + let hidden = "hidden" in + let kernel_execution = + Configuration.{config.kernel_execution with preimages = hidden} + in + let sequencer = + Option.map + (fun (sequencer_config : sequencer) -> + {sequencer_config with sequencer = Client_keys.sk_uri_of_string hidden}) + config.sequencer + in + let observer = + Option.map + (fun (observer : observer) -> + {observer with evm_node_endpoint = Uri.of_string hidden}) + config.observer + in + let proxy : proxy = + let evm_node_endpoint = + Option.map (fun _ -> Uri.of_string hidden) config.proxy.evm_node_endpoint + in + {config.proxy with evm_node_endpoint} + in + + let config = + { + config with + rollup_node_endpoint = Uri.of_string hidden; + kernel_execution; + sequencer; + threshold_encryption_sequencer = None; + proxy; + observer; + private_rpc = None; + } + in + + Data_encoding.Json.construct + ~include_default_fields:`Always + (Configuration.encoding hidden) + config + +let health_check_handler ?delegate_to () = + match delegate_to with + | None -> + let open Lwt_result_syntax in + let* () = fail_when (Metrics.is_bootstrapping ()) Node_is_bootstrapping in + return_unit + | Some evm_node_endpoint -> + Rollup_services.call_service + ~keep_alive:false + ~base:evm_node_endpoint + ~media_types:[Media_type.json] + health_check_service + () + () + () + let version dir = - Directory.register0 dir version_service (fun () () -> + Evm_directory.register0 dir version_service (fun () () -> Lwt.return_ok client_version) let configuration config dir = - Directory.register0 dir configuration_service (fun () () -> - let open Configuration in - (* Hide some parts of the configuration. *) - let hidden = "hidden" in - let kernel_execution = - Configuration.{config.kernel_execution with preimages = hidden} - in - let sequencer = - Option.map - (fun (sequencer_config : sequencer) -> - { - sequencer_config with - sequencer = Client_keys.sk_uri_of_string hidden; - }) - config.sequencer - in - let observer = - Option.map - (fun (observer : observer) -> - {observer with evm_node_endpoint = Uri.of_string hidden}) - config.observer - in - let proxy : proxy = - let evm_node_endpoint = - Option.map - (fun _ -> Uri.of_string hidden) - config.proxy.evm_node_endpoint - in - {config.proxy with evm_node_endpoint} - in - - let config = - { - config with - rollup_node_endpoint = Uri.of_string hidden; - kernel_execution; - sequencer; - threshold_encryption_sequencer = None; - proxy; - observer; - private_rpc = None; - } - in - - Lwt.return_ok - (Data_encoding.Json.construct - ~include_default_fields:`Always - (Configuration.encoding hidden) - config)) + Evm_directory.register0 dir configuration_service (fun () () -> + configuration_handler config |> Lwt.return_ok) let health_check ?delegate_to dir = - let handler = - match delegate_to with - | None -> - fun () () -> - let open Lwt_result_syntax in - let* () = - fail_when (Metrics.is_bootstrapping ()) Node_is_bootstrapping - in - return_unit - | Some evm_node_endpoint -> - fun () () -> - Rollup_services.call_service - ~keep_alive:false - ~base:evm_node_endpoint - ~media_types:[Media_type.json] - health_check_service - () - () - () - in - Directory.register0 dir health_check_service handler + Evm_directory.register0 dir health_check_service (fun () () -> + health_check_handler ?delegate_to ()) (* The node can either take a single request or multiple requests at once. *) @@ -853,26 +847,30 @@ let can_process_batch size = function | Configuration.Limit l -> size <= l | Unlimited -> true +let dispatch_handler (rpc : Configuration.rpc) config ctx dispatch_request + (input : JSONRPC.request batched_request) = + let open Lwt_syntax in + match input with + | Singleton request -> + let* response = dispatch_request config ctx request in + return (Singleton response) + | Batch requests -> + let process = + if can_process_batch (List.length requests) rpc.batch_limit then + dispatch_request config ctx + else fun req -> + let value = + Error Rpc_errors.(invalid_request "too many requests in batch") + in + Lwt.return JSONRPC.{value; id = req.id} + in + let* outputs = List.map_s process requests in + return (Batch outputs) + let generic_dispatch (rpc : Configuration.rpc) config ctx dir path dispatch_request = - Directory.register0 dir (dispatch_service ~path) (fun () input -> - let open Lwt_result_syntax in - match input with - | Singleton request -> - let*! response = dispatch_request config ctx request in - return (Singleton response) - | Batch requests -> - let process = - if can_process_batch (List.length requests) rpc.batch_limit then - dispatch_request config ctx - else fun req -> - let value = - Error Rpc_errors.(invalid_request "too many requests in batch") - in - Lwt.return JSONRPC.{value; id = req.id} - in - let*! outputs = List.map_s process requests in - return (Batch outputs)) + Evm_directory.register0 dir (dispatch_service ~path) (fun () input -> + dispatch_handler rpc config ctx dispatch_request input |> Lwt_result.ok) let dispatch_public (rpc : Configuration.rpc) config ctx dir = generic_dispatch rpc config ctx dir Path.root (dispatch_request rpc) @@ -889,7 +887,8 @@ let dispatch_private (rpc : Configuration.rpc) ~block_production config ctx dir let directory ?delegate_health_check_to rpc config ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) = - Directory.empty |> version |> configuration config + Evm_directory.empty config.experimental_features.rpc_server + |> version |> configuration config |> health_check ?delegate_to:delegate_health_check_to |> dispatch_public rpc @@ -897,12 +896,15 @@ let directory ?delegate_health_check_to rpc config ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) let private_directory rpc config - ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) = - Directory.empty |> version + ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) + ~block_production = + Evm_directory.empty config.experimental_features.rpc_server + |> version |> dispatch_private rpc config ((module Rollup_node_rpc : Services_backend_sig.S), smart_rollup_address) + ~block_production let call (type input output) (module R : Rpc_encodings.METHOD -- GitLab From fe773a55756df2e0497f69f6434ca808c57b90db Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 9 Dec 2024 09:27:50 +0100 Subject: [PATCH 06/11] EVM node: show RPC server backend in event --- etherlink/bin_node/config/configuration.mli | 3 +++ etherlink/bin_node/lib_dev/events.ml | 19 ++++++++++------- etherlink/bin_node/lib_dev/events.mli | 23 +++++++++++++++------ etherlink/bin_node/lib_dev/rpc_server.ml | 2 ++ 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/etherlink/bin_node/config/configuration.mli b/etherlink/bin_node/config/configuration.mli index 52f9e21d10b5..60a2ee300d9e 100644 --- a/etherlink/bin_node/config/configuration.mli +++ b/etherlink/bin_node/config/configuration.mli @@ -161,6 +161,9 @@ val native_execution_policy_encoding : native_execution_policy Data_encoding.t (** [encoding data_dir] is the encoding of {!t} based on data dir [data_dir]. *) val encoding : string -> t Data_encoding.t +(** Encoding for {!type-rpc_server}. *) +val rpc_server_encoding : rpc_server Data_encoding.t + (** [default_data_dir] is the default value for [data_dir]. *) val default_data_dir : string diff --git a/etherlink/bin_node/lib_dev/events.ml b/etherlink/bin_node/lib_dev/events.ml index a573fc126005..17ceef356937 100644 --- a/etherlink/bin_node/lib_dev/events.ml +++ b/etherlink/bin_node/lib_dev/events.ml @@ -66,22 +66,26 @@ let catching_up_evm_event = ("to", Data_encoding.int32) let event_is_ready = - Internal_event.Simple.declare_2 + Internal_event.Simple.declare_3 ~section ~name:"is_ready" - ~msg:"the EVM node is listening to {addr}:{port}" + ~msg:"the EVM node RPC server ({backend}) is listening to {addr}:{port}" ~level:Notice ("addr", Data_encoding.string) ("port", Data_encoding.uint16) + ("backend", Configuration.rpc_server_encoding) let event_private_server_is_ready = - declare_2 + declare_3 ~section ~name:"private_server_is_ready" - ~msg:"the EVM node private RPC server is listening to {addr}:{port}" + ~msg: + "the EVM node private RPC server ({backend}) is listening to \ + {addr}:{port}" ~level:Notice ("addr", Data_encoding.string) ("port", Data_encoding.uint16) + ("backend", Configuration.rpc_server_encoding) let event_shutdown_node = Internal_event.Simple.declare_1 @@ -247,10 +251,11 @@ let ignored_kernel_arg () = emit ignored_kernel_arg () let catching_up_evm_event ~from ~to_ = emit catching_up_evm_event (from, to_) -let is_ready ~rpc_addr ~rpc_port = emit event_is_ready (rpc_addr, rpc_port) +let is_ready ~rpc_addr ~rpc_port ~backend = + emit event_is_ready (rpc_addr, rpc_port, backend) -let private_server_is_ready ~rpc_addr ~rpc_port = - emit event_private_server_is_ready (rpc_addr, rpc_port) +let private_server_is_ready ~rpc_addr ~rpc_port ~backend = + emit event_private_server_is_ready (rpc_addr, rpc_port, backend) let shutdown_rpc_server ~private_ = emit (event_shutdown_rpc_server ~private_) () diff --git a/etherlink/bin_node/lib_dev/events.mli b/etherlink/bin_node/lib_dev/events.mli index 08c0fe29cc13..5f30c175e23d 100644 --- a/etherlink/bin_node/lib_dev/events.mli +++ b/etherlink/bin_node/lib_dev/events.mli @@ -38,13 +38,24 @@ val ignored_kernel_arg : unit -> unit Lwt.t node from L1 level [from] to [to_]. *) val catching_up_evm_event : from:int32 -> to_:int32 -> unit Lwt.t -(** [is_ready ~rpc_addr ~rpc_port] advertises that the sequencer is - ready and listens to [rpc_addr]:[rpc_port]. *) -val is_ready : rpc_addr:string -> rpc_port:int -> unit Lwt.t +(** [is_ready ~rpc_addr ~rpc_port ~backend] advertises that the + sequencer is ready and listens to [rpc_addr]:[rpc_port]. *) +val is_ready : + rpc_addr:string -> + rpc_port:int -> + backend:Configuration.rpc_server -> + unit Lwt.t + +(** [private_server_is_ready ~rpc_addr ~rpc_port ~backend] + advertises that the private rpc server is ready and listens to + [rpc_addr]:[rpc_port]. *) +val private_server_is_ready : + rpc_addr:string -> + rpc_port:int -> + backend:Configuration.rpc_server -> + unit Lwt.t -(** [private_server_is_ready ~rpc_addr ~rpc_port] advertises that the - private rpc server is ready and listens to [rpc_addr]:[rpc_port]. *) -val private_server_is_ready : rpc_addr:string -> rpc_port:int -> unit Lwt.t +val rpc_server_error : exn -> unit (** [shutdown_rpc_server ~private_ ()] advertises that the RPC server was shut down, [private_] tells whether it is the private server diff --git a/etherlink/bin_node/lib_dev/rpc_server.ml b/etherlink/bin_node/lib_dev/rpc_server.ml index cce0306dbcbc..15ee95dd1cc8 100644 --- a/etherlink/bin_node/lib_dev/rpc_server.ml +++ b/etherlink/bin_node/lib_dev/rpc_server.ml @@ -132,6 +132,7 @@ let start_public_server ?delegate_health_check_to ?evm_services Events.is_ready ~rpc_addr:config.public_rpc.addr ~rpc_port:config.public_rpc.port + ~backend:config.experimental_features.rpc_server in return finalizer @@ -147,6 +148,7 @@ let start_private_server ?(block_production = `Disabled) config ctxt = Events.private_server_is_ready ~rpc_addr:private_rpc.addr ~rpc_port:private_rpc.port + ~backend:config.experimental_features.rpc_server in return finalizer | None -> return (fun () -> Lwt_syntax.return_unit) -- GitLab From 69c02c971f380de3fa4abafecc4f1ddbce18a0ab Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 5 Dec 2024 17:21:29 +0100 Subject: [PATCH 07/11] Evm node: stop if cannot bind to RPC listening port --- etherlink/bin_node/lib_dev/events.ml | 11 +++++++++++ etherlink/bin_node/lib_dev/rpc_server.ml | 8 ++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/etherlink/bin_node/lib_dev/events.ml b/etherlink/bin_node/lib_dev/events.ml index 17ceef356937..e69fc268c5ad 100644 --- a/etherlink/bin_node/lib_dev/events.ml +++ b/etherlink/bin_node/lib_dev/events.ml @@ -87,6 +87,14 @@ let event_private_server_is_ready = ("port", Data_encoding.uint16) ("backend", Configuration.rpc_server_encoding) +let event_rpc_server_error = + declare_1 + ~section + ~name:"rpc_server_error" + ~msg:"RPC server error: {exception}" + ~level:Error + ("exception", Data_encoding.string) + let event_shutdown_node = Internal_event.Simple.declare_1 ~section @@ -257,6 +265,9 @@ let is_ready ~rpc_addr ~rpc_port ~backend = let private_server_is_ready ~rpc_addr ~rpc_port ~backend = emit event_private_server_is_ready (rpc_addr, rpc_port, backend) +let rpc_server_error exn = + emit__dont_wait__use_with_care event_rpc_server_error (Printexc.to_string exn) + let shutdown_rpc_server ~private_ = emit (event_shutdown_rpc_server ~private_) () diff --git a/etherlink/bin_node/lib_dev/rpc_server.ml b/etherlink/bin_node/lib_dev/rpc_server.ml index 15ee95dd1cc8..216ae82d8585 100644 --- a/etherlink/bin_node/lib_dev/rpc_server.ml +++ b/etherlink/bin_node/lib_dev/rpc_server.ml @@ -101,8 +101,12 @@ module Dream = struct Lwt.dont_wait (fun () -> routes |> Dream.router |> Dream.serve ~interface:addr ~port ~stop) - (fun exn -> - Format.eprintf "Dream server error: %s@." (Printexc.to_string exn)) ; + (function + | Unix.Unix_error (Unix.EADDRINUSE, _, _) -> + Logs.err (fun m -> + m "Cannot start RPC server on port %d, already in use." port) ; + exit 1 + | exn -> Events.rpc_server_error exn) ; return shutdown end -- GitLab From bfedf34d8263b7e6f6c5b23453de782ad625598e Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Wed, 13 Nov 2024 10:31:39 +0100 Subject: [PATCH 08/11] Test: fix ports conflict --- etherlink/tezt/lib/evm_node.ml | 122 ++++++++++++++++++++++++++ etherlink/tezt/lib/evm_node.mli | 3 + etherlink/tezt/tests/evm_sequencer.ml | 19 ++-- 3 files changed, 137 insertions(+), 7 deletions(-) diff --git a/etherlink/tezt/lib/evm_node.ml b/etherlink/tezt/lib/evm_node.ml index 302dfa3e29a0..8f606e28a252 100644 --- a/etherlink/tezt/lib/evm_node.ml +++ b/etherlink/tezt/lib/evm_node.ml @@ -580,6 +580,128 @@ let wait_for_processed_l1_level ?level evm_node = | None -> Some () | Some level -> if level = event_level then Some () else None +let mode_with_new_private_rpc (mode : mode) = + match mode with + | Observer + { + initial_kernel; + preimages_dir; + private_rpc_port = Some _; + rollup_node_endpoint; + } -> + Observer + { + initial_kernel; + preimages_dir; + private_rpc_port = Some (Port.fresh ()); + rollup_node_endpoint; + } + | Sequencer + { + initial_kernel; + preimage_dir; + private_rpc_port = Some _; + time_between_blocks; + sequencer; + genesis_timestamp; + max_blueprints_lag; + max_blueprints_ahead; + max_blueprints_catchup; + catchup_cooldown; + max_number_of_chunks; + wallet_dir; + tx_pool_timeout_limit; + tx_pool_addr_limit; + tx_pool_tx_per_addr_limit; + dal_slots; + } -> + Sequencer + { + initial_kernel; + preimage_dir; + private_rpc_port = Some (Port.fresh ()); + time_between_blocks; + sequencer; + genesis_timestamp; + max_blueprints_lag; + max_blueprints_ahead; + max_blueprints_catchup; + catchup_cooldown; + max_number_of_chunks; + wallet_dir; + tx_pool_timeout_limit; + tx_pool_addr_limit; + tx_pool_tx_per_addr_limit; + dal_slots; + } + | Sandbox + { + initial_kernel; + preimage_dir; + private_rpc_port = Some _; + time_between_blocks; + genesis_timestamp; + max_number_of_chunks; + wallet_dir; + tx_pool_timeout_limit; + tx_pool_addr_limit; + tx_pool_tx_per_addr_limit; + } -> + Sandbox + { + initial_kernel; + preimage_dir; + private_rpc_port = Some (Port.fresh ()); + time_between_blocks; + genesis_timestamp; + max_number_of_chunks; + wallet_dir; + tx_pool_timeout_limit; + tx_pool_addr_limit; + tx_pool_tx_per_addr_limit; + } + | Threshold_encryption_sequencer + { + initial_kernel; + preimage_dir; + private_rpc_port = Some _; + time_between_blocks; + sequencer; + genesis_timestamp; + max_blueprints_lag; + max_blueprints_ahead; + max_blueprints_catchup; + catchup_cooldown; + max_number_of_chunks; + wallet_dir; + tx_pool_timeout_limit; + tx_pool_addr_limit; + tx_pool_tx_per_addr_limit; + sequencer_sidecar_endpoint; + dal_slots; + } -> + Threshold_encryption_sequencer + { + initial_kernel; + preimage_dir; + private_rpc_port = Some (Port.fresh ()); + time_between_blocks; + sequencer; + genesis_timestamp; + max_blueprints_lag; + max_blueprints_ahead; + max_blueprints_catchup; + catchup_cooldown; + max_number_of_chunks; + wallet_dir; + tx_pool_timeout_limit; + tx_pool_addr_limit; + tx_pool_tx_per_addr_limit; + sequencer_sidecar_endpoint; + dal_slots; + } + | _ -> mode + let create ?(path = Uses.path Constant.octez_evm_node) ?name ?runner ?(mode = Proxy) ?data_dir ?rpc_addr ?rpc_port ?restricted_rpcs endpoint = let arguments, rpc_addr, rpc_port = diff --git a/etherlink/tezt/lib/evm_node.mli b/etherlink/tezt/lib/evm_node.mli index 9721dd89abdd..0af54a622d8f 100644 --- a/etherlink/tezt/lib/evm_node.mli +++ b/etherlink/tezt/lib/evm_node.mli @@ -517,3 +517,6 @@ val man : ?path:string -> ?hooks:Process_hooks.t -> unit -> unit Lwt.t val describe_config : ?path:string -> ?hooks:Process_hooks.t -> unit -> unit Lwt.t + +(** Returns the [mode] with a fresh private RPC port if one was present. *) +val mode_with_new_private_rpc : mode -> mode diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 5187d849a285..1413eefe59bc 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -779,7 +779,7 @@ let test_snapshots_import_empty = in Log.info "Create new sequencer from snapshot." ; let new_sequencer = - let mode = Evm_node.mode sequencer in + let mode = Evm_node.mode sequencer |> Evm_node.mode_with_new_private_rpc in Evm_node.create ~mode (Sc_rollup_node.endpoint sc_rollup_node) in let* () = Process.check @@ Evm_node.spawn_init_config new_sequencer in @@ -860,7 +860,7 @@ let test_snapshots_reexport = in Log.info "Create new sequencer from snapshot." ; let new_sequencer = - let mode = Evm_node.mode sequencer in + let mode = Evm_node.mode sequencer |> Evm_node.mode_with_new_private_rpc in Evm_node.create ~mode (Sc_rollup_node.endpoint sc_rollup_node) in let* () = Process.check @@ Evm_node.spawn_init_config new_sequencer in @@ -2133,7 +2133,7 @@ let test_delayed_deposit_from_init_rollup_node = (* Run a new sequencer that is initialized from a rollup node that has the delayed deposit in its state. *) let new_sequencer = - let mode = Evm_node.mode sequencer in + let mode = Evm_node.mode sequencer |> Evm_node.mode_with_new_private_rpc in Evm_node.create ~mode (Sc_rollup_node.endpoint sc_rollup_node) in let* () = Process.check @@ Evm_node.spawn_init_config new_sequencer in @@ -5121,20 +5121,25 @@ let test_preimages_endpoint = let* () = Process.check @@ Evm_node.spawn_init_config new_sequencer in (* Prepares the observer without [preimages-dir], to force the use of preimages endpoint. *) - let observer_mode_without_preimages_dir = + let observer_mode_without_preimages_dir () = match Evm_node.mode observer with | Evm_node.Observer mode -> - Evm_node.Observer {mode with preimages_dir = None} + Evm_node.Observer + { + mode with + preimages_dir = None; + private_rpc_port = Some (Port.fresh ()); + } | _ -> assert false in let new_observer = Evm_node.create - ~mode:observer_mode_without_preimages_dir + ~mode:(observer_mode_without_preimages_dir ()) (Evm_node.endpoint new_sequencer) in let new_observer2 = Evm_node.create - ~mode:observer_mode_without_preimages_dir + ~mode:(observer_mode_without_preimages_dir ()) (Evm_node.endpoint new_observer) in -- GitLab From eff0e56e82deb69f13d5ac11b13001276b434e9a Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 2 Dec 2024 16:28:56 +0100 Subject: [PATCH 09/11] Tests: patch retry connect observer tests --- etherlink/tezt/tests/evm_sequencer.ml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 1413eefe59bc..0b340980528d 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -2831,14 +2831,12 @@ let test_observer_timeout_when_necessary = request the new time between block, and will keep waiting for new blocks to arrive every 3s. *) let* () = Evm_node.terminate sequencer in - let* () = - Evm_node.run sequencer ~extra_arguments:["--time-between-blocks"; "none"] - in - (* After enough time, the observer considers its connection with the sequencer is stalled and tries to reconnect. *) - let* () = Evm_node.wait_for_retrying_connect observer in - + let* () = Evm_node.wait_for_retrying_connect ~timeout:5. observer + and* () = + Evm_node.run sequencer ~extra_arguments:["--time-between-blocks"; "none"] + in (* We produce a block, and verify that the observer nodes correctly applies it. *) let* _ = produce_block sequencer in -- GitLab From 17362ee3edcfbc28786de34b86ae27cb450f1f76 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 3 Dec 2024 21:21:14 +0100 Subject: [PATCH 10/11] Tests: allow to select RPC server implementation --- etherlink/tezt/lib/evm_node.ml | 7 ++++++- etherlink/tezt/lib/evm_node.mli | 11 +++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/etherlink/tezt/lib/evm_node.ml b/etherlink/tezt/lib/evm_node.ml index 8f606e28a252..77ac098f26b0 100644 --- a/etherlink/tezt/lib/evm_node.ml +++ b/etherlink/tezt/lib/evm_node.ml @@ -1174,10 +1174,12 @@ type garbage_collector = { history_to_keep_in_seconds : int; } +type rpc_server = Resto | Dream + let patch_config_with_experimental_feature ?(drop_duplicate_when_injection = false) ?(node_transaction_validation = false) ?(block_storage_sqlite3 = true) - ?(next_wasm_runtime = true) ?garbage_collector () = + ?(next_wasm_runtime = true) ?garbage_collector ?rpc_server () = let conditional_json_put ~name cond value_json json = if cond then JSON.put @@ -1227,6 +1229,9 @@ let patch_config_with_experimental_feature ( "history_to_keep_in_seconds", `Float (Int.to_float history_to_keep_in_seconds) ); ]) + |> optional_json_put ~name:"rpc_server" rpc_server (function + | Resto -> `String "resto" + | Dream -> `String "dream") let init ?patch_config ?name ?runner ?mode ?data_dir ?rpc_addr ?rpc_port ?restricted_rpcs rollup_node = diff --git a/etherlink/tezt/lib/evm_node.mli b/etherlink/tezt/lib/evm_node.mli index 0af54a622d8f..6356f1a36065 100644 --- a/etherlink/tezt/lib/evm_node.mli +++ b/etherlink/tezt/lib/evm_node.mli @@ -256,10 +256,12 @@ type garbage_collector = { history_to_keep_in_seconds : int; } -(** [patch_config_with_experimental_feature - ?node_transaction_validation ?drop_duplicate_when_injection - ?block_storage_sqlite3 ?next_wasm_runtime json_config] patches a - config to add experimental feature. Each optional argument add the +type rpc_server = Resto | Dream + +(** [patch_config_with_experimental_feature ?node_transaction_validation + ?drop_duplicate_when_injection ?block_storage_sqlite3 ?next_wasm_runtime + ?rpc_server json_config] + patches a config to add experimental feature. Each optional argument add the correspondent experimental feature. *) val patch_config_with_experimental_feature : ?drop_duplicate_when_injection:bool -> @@ -267,6 +269,7 @@ val patch_config_with_experimental_feature : ?block_storage_sqlite3:bool -> ?next_wasm_runtime:bool -> ?garbage_collector:garbage_collector -> + ?rpc_server:rpc_server -> unit -> JSON.t -> JSON.t -- GitLab From 1c50740fd287b66765a017c3235be66a27fa5660 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 5 Dec 2024 17:25:25 +0100 Subject: [PATCH 11/11] Doc: changelog --- etherlink/CHANGES_NODE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etherlink/CHANGES_NODE.md b/etherlink/CHANGES_NODE.md index d7af0752ac2e..9b0d2f3aa441 100644 --- a/etherlink/CHANGES_NODE.md +++ b/etherlink/CHANGES_NODE.md @@ -14,6 +14,9 @@ - The sequencer can now uses the experimental features `drop_duplicate_in_injection` if activated when publishing blueprints. (!15867) +- Experimental support for alternative RPC server backend + [Dream](https://aantron.github.io/dream) with feature flag + `experimental_features.rpc_server = "dream"`. (!15560) ### Bug fixes -- GitLab