diff --git a/.gitignore b/.gitignore index b5316bb2253ede3f15926b5d752660915dab5d8e..33f48eafb522f22387886928db7aa1cd9f9f98ab 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,7 @@ __pycache__ /tezos-tps-evaluation-* /octez-tps-evaluation-* /tezos-smart-rollup-node-* -/octez-smart-rollup-node-* +/octez-smart-rollup-node* /tezos-smart-rollup-client-* /octez-smart-rollup-client-* /octez-smart-rollup-wasm-* diff --git a/.gitlab/ci/jobs/packaging/opam_package.yml b/.gitlab/ci/jobs/packaging/opam_package.yml index d0574da35a53c267c45feda297126955deebc9ef..71d8406df85532a66cce320deff20195855b1d9b 100644 --- a/.gitlab/ci/jobs/packaging/opam_package.yml +++ b/.gitlab/ci/jobs/packaging/opam_package.yml @@ -406,6 +406,8 @@ opam:octez-smart-rollup-client-PtNairob: # Ignoring unreleased package octez-smart-rollup-client-alpha. +# Ignoring unreleased package octez-smart-rollup-node. + opam:octez-smart-rollup-node-PtMumbai: extends: - .opam_template @@ -533,7 +535,7 @@ opam:tezos-base-test-helpers: opam:tezos-clic: extends: - .opam_template - - .rules_template__trigger_opam_batch_6 + - .rules_template__trigger_opam_batch_7 variables: package: tezos-clic @@ -1688,6 +1690,6 @@ opam:tezt-performance-regression: opam:tezt-tezos: extends: - .opam_template - - .rules_template__trigger_opam_batch_7 + - .rules_template__trigger_opam_batch_6 variables: package: tezt-tezos diff --git a/docs/alpha/smart_rollups.rst b/docs/alpha/smart_rollups.rst index 4ce87ddf7f3fa844af8d9b704bc88d89c274529f..4742a02796a900cfa41969720807853b89679b23 100644 --- a/docs/alpha/smart_rollups.rst +++ b/docs/alpha/smart_rollups.rst @@ -457,7 +457,7 @@ The rollup node can then be run with: .. code:: sh - octez-smart-rollup-node-alpha --base-dir "${OCLIENT_DIR}" \ + octez-smart-rollup-node --base-dir "${OCLIENT_DIR}" \ run operator for "${SOR_ADDR}" \ with operators "${OPERATOR_ADDR}" \ --data-dir "${ROLLUP_NODE_DIR}" @@ -530,7 +530,7 @@ uses the same arguments as the ``run`` command: .. code:: sh - octez-smart-rollup-node-alpha --base-dir "${OCLIENT_DIR}" \ + octez-smart-rollup-node --base-dir "${OCLIENT_DIR}" \ init operator config for "${SOR_ADDR}" \ with operators "${OPERATOR_ADDR}" \ --data-dir "${ROLLUP_NODE_DIR}" @@ -562,7 +562,7 @@ The rollup node can now be run with just: .. code:: sh - octez-smart-rollup-node-alpha -d "${OCLIENT_DIR}" run --data-dir ${ROLLUP_NODE_DIR} + octez-smart-rollup-node -d "${OCLIENT_DIR}" run --data-dir ${ROLLUP_NODE_DIR} The configuration will be read from ``${ROLLUP_NODE_DIR}/config.json``. @@ -571,7 +571,7 @@ Rollup node in a sandbox The node can also be tested locally with a sandbox environment. (See :doc:`sandbox documentation <../user/sandbox>`.) -Once you initialized the "sandboxed" client data with ``./src/bin_client/octez-init-sandboxed-client.sh``, you can run a sandboxed rollup node with ``octez-smart-rollup-node-alpha run``. +Once you initialized the "sandboxed" client data with ``./src/bin_client/octez-init-sandboxed-client.sh``, you can run a sandboxed rollup node with ``octez-smart-rollup-node run``. A temporary directory ``/tmp/tezos-smart-rollup-node.xxxxxxxx`` will be used. However, a specific data directory can be set with the environment variable ``SCORU_DATA_DIR``. diff --git a/dune-project b/dune-project index ddbe0551aad43147fab3921b5d88e9cc699785ea..54d1ba903d5d9a6dbbd6e89973fed7ea3379f1e8 100644 --- a/dune-project +++ b/dune-project @@ -39,6 +39,7 @@ (package (name octez-smart-rollup-client-PtMumbai)) (package (name octez-smart-rollup-client-PtNairob)) (package (name octez-smart-rollup-client-alpha)) +(package (name octez-smart-rollup-node)) (package (name octez-smart-rollup-node-PtMumbai)) (package (name octez-smart-rollup-node-PtNairob)) (package (name octez-smart-rollup-node-alpha)) diff --git a/manifest/main.ml b/manifest/main.ml index e6eb924f5c6fd3b8d162f3fd3c9ddac8045d79f3..f582f76e5f0fda77493d06b8a98d3ca36288d78c 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -4162,6 +4162,10 @@ let octez_sc_rollup_node_lib = cohttp_lwt_unix; octez_node_config; prometheus_app; + octez_client_base |> open_; + octez_client_base_unix |> open_; + octez_rpc_http_server; + octez_layer2_store |> open_; octez_crawler |> open_; octez_injector |> open_; octez_sc_rollup_lib |> open_; @@ -4352,6 +4356,8 @@ module Protocol : sig val dac : t -> target option + val sc_rollup : t -> target option + val parameters_exn : t -> target val benchmarks_proto_exn : t -> target @@ -4467,6 +4473,7 @@ end = struct plugin_registerer : target option; dal : target option; dac : target option; + sc_rollup : target option; test_helpers : target option; parameters : target option; benchmarks_proto : target option; @@ -4475,8 +4482,8 @@ end = struct let make ?client ?client_commands ?client_commands_registration ?baking_commands_registration ?plugin ?plugin_registerer ?dal ?dac - ?test_helpers ?parameters ?benchmarks_proto ?baking ~status ~name ~main - ~embedded () = + ?sc_rollup ?test_helpers ?parameters ?benchmarks_proto ?baking ~status + ~name ~main ~embedded () = { status; name; @@ -4490,6 +4497,7 @@ end = struct plugin_registerer; dal; dac; + sc_rollup; test_helpers; parameters; benchmarks_proto; @@ -4546,6 +4554,8 @@ end = struct let dac p = p.dac + let sc_rollup p = p.sc_rollup + let parameters_exn p = mandatory "parameters" p p.parameters let benchmarks_proto_exn p = mandatory "benchmarks_proto" p p.benchmarks_proto @@ -6427,6 +6437,7 @@ module Protocol = Protocol ?plugin_registerer ?dal ?dac + ?sc_rollup:octez_sc_rollup_node ?test_helpers ?parameters ?benchmarks_proto @@ -7295,6 +7306,42 @@ let _octez_dac_node = ] @ protocol_deps) +let _octez_sc_rollup_node = + let protocol_deps = + let deps_for_protocol protocol = + let is_optional = + match (Protocol.status protocol, Protocol.number protocol) with + | Active, V _ -> false + | (Frozen | Overridden | Not_mainnet), _ | Active, (Alpha | Other) -> + true + in + let targets = List.filter_map Fun.id [Protocol.sc_rollup protocol] in + if is_optional then List.map optional targets else targets + in + List.map deps_for_protocol Protocol.all |> List.flatten + in + public_exe + "octez-smart-rollup-node" + ~internal_name:"main_sc_rollup_node" + ~path:"src/bin_sc_rollup_node" + ~synopsis:"Octez: Smart rollup node" + ~release_status:Experimental + ~linkall:true + ~with_macos_security_framework:true + ~deps: + ([ + octez_base |> open_ |> open_ ~m:"TzPervasives" + |> open_ ~m:"TzPervasives.Error_monad.Legacy_monad_globals"; + octez_clic; + octez_shell_services |> open_; + octez_client_base |> open_; + octez_client_base_unix |> open_; + octez_client_commands |> open_; + octez_sc_rollup_lib |> open_; + octez_sc_rollup_node_lib |> open_; + ] + @ protocol_deps) + let _octez_scoru_wasm_debugger = public_exe (sf "octez-smart-rollup-wasm-debugger") diff --git a/opam/octez-sc-rollup-node.opam b/opam/octez-sc-rollup-node.opam index a12c87cf0c31911811b9c7837aef3981fc6c9ec9..808576e1dde29ff5c34e2378678700ed8d07f71e 100644 --- a/opam/octez-sc-rollup-node.opam +++ b/opam/octez-sc-rollup-node.opam @@ -16,6 +16,10 @@ depends: [ "cohttp-lwt-unix" { >= "4.0.0" } "octez-node-config" "prometheus-app" { >= "1.2" } + "tezos-client-base" + "tezos-client-base-unix" + "tezos-rpc-http-server" + "tezos-layer2-store" "octez-crawler" "octez-injector" "octez-sc-rollup" diff --git a/opam/octez-smart-rollup-node.opam b/opam/octez-smart-rollup-node.opam new file mode 100644 index 0000000000000000000000000000000000000000..8525cc0f15d04feb7873de58438f6f58d87cae01 --- /dev/null +++ b/opam/octez-smart-rollup-node.opam @@ -0,0 +1,32 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "3.0" } + "ocaml" { >= "4.14" } + "tezos-base" + "tezos-clic" + "tezos-shell-services" + "tezos-client-base" + "tezos-client-base-unix" + "tezos-client-commands" + "octez-sc-rollup" + "octez-sc-rollup-node" + "octez-smart-rollup-node-PtMumbai" + "octez-smart-rollup-node-PtNairob" +] +depopts: [ + "octez-smart-rollup-node-alpha" +] +build: [ + ["rm" "-r" "vendors"] + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +synopsis: "Octez: Smart rollup node" diff --git a/script-inputs/experimental-executables b/script-inputs/experimental-executables index 7124e4e4cfe9224875a2dd0d541a5569a0891b7c..f861e5b77b7d19c2f527c84fe91a933b93fa41aa 100644 --- a/script-inputs/experimental-executables +++ b/script-inputs/experimental-executables @@ -1,4 +1,5 @@ octez-evm-proxy-server +octez-smart-rollup-node octez-dac-node octez-dal-node octez-smart-rollup-node-alpha diff --git a/src/bin_sc_rollup_node/dune b/src/bin_sc_rollup_node/dune new file mode 100644 index 0000000000000000000000000000000000000000..8449f88cb6d6cf6783a7fe6fe0b4e9ee7d1ec848 --- /dev/null +++ b/src/bin_sc_rollup_node/dune @@ -0,0 +1,42 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(executable + (name main_sc_rollup_node) + (public_name octez-smart-rollup-node) + (package octez-smart-rollup-node) + (instrumentation (backend bisect_ppx)) + (libraries + tezos-base + tezos-clic + tezos-shell-services + tezos-client-base + tezos-client-base-unix + tezos-client-commands + octez-sc-rollup + octez-sc-rollup-node + octez_smart_rollup_node_PtMumbai + octez_smart_rollup_node_PtNairob + (select void_for_linking-octez_smart_rollup_node_alpha from + (octez_smart_rollup_node_alpha -> void_for_linking-octez_smart_rollup_node_alpha.empty) + (-> void_for_linking-octez_smart_rollup_node_alpha.empty))) + (link_flags + (:standard) + (:include %{workspace_root}/static-link-flags.sexp) + (:include %{workspace_root}/macos-link-flags.sexp) + (-linkall)) + (flags + (:standard) + -open Tezos_base + -open Tezos_base.TzPervasives + -open Tezos_base.TzPervasives.Error_monad.Legacy_monad_globals + -open Tezos_shell_services + -open Tezos_client_base + -open Tezos_client_base_unix + -open Tezos_client_commands + -open Octez_sc_rollup + -open Octez_sc_rollup_node)) + +(rule + (action + (progn (write-file void_for_linking-octez_smart_rollup_node_alpha.empty "")))) diff --git a/src/bin_sc_rollup_node/main_sc_rollup_node.ml b/src/bin_sc_rollup_node/main_sc_rollup_node.ml new file mode 100644 index 0000000000000000000000000000000000000000..3b34d5d0d90569c2d0b1bb2a369c490191e0cf8d --- /dev/null +++ b/src/bin_sc_rollup_node/main_sc_rollup_node.ml @@ -0,0 +1,732 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2021 Nomadic Labs, *) +(* Copyright (c) 2022 Trili Tech, *) +(* Copyright (c) 2023 Functori, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +let string_parameter = Tezos_clic.parameter (fun _ x -> return x) + +let int_parameter = + Tezos_clic.parameter (fun (cctxt : #Client_context.full) p -> + try return (int_of_string p) with _ -> cctxt#error "Cannot read int") + +let z_parameter = + Tezos_clic.parameter (fun (cctxt : #Client_context.full) p -> + try return (Z.of_string p) with _ -> cctxt#error "Cannot read integer") + +let force_switch = + Tezos_clic.switch + ~long:"force" + ~doc:"Overwrites the configuration file when it exists." + () + +let sc_rollup_address_parameter = + Tezos_clic.parameter (fun _ s -> + match Sc_rollup_address.of_b58check_opt s with + | None -> failwith "Invalid smart rollup address" + | Some addr -> return addr) + +let sc_rollup_address_param = + Tezos_clic.param + ~name:"smart-rollup-address" + ~desc:"The smart rollup address" + sc_rollup_address_parameter + +let sc_rollup_address_arg = + Tezos_clic.arg + ~long:"rollup" + ~placeholder:"smart-rollup-address" + ~doc:"The smart rollup address (required when no configuration file exists)" + sc_rollup_address_parameter + +let sc_rollup_node_operator_param = + let open Lwt_result_syntax in + Tezos_clic.param + ~name:"operator" + ~desc: + (Printf.sprintf + "Public key hash, or alias, of a smart rollup node operator. An \ + operator can be specialized to a particular purpose by prefixing its \ + key or alias by said purpose, e.g. publish:alias_of_my_operator. The \ + possible purposes are: %s." + (String.concat ", " + @@ Configuration.(List.map string_of_purpose purposes))) + @@ Tezos_clic.parameter + @@ fun cctxt s -> + let parse_pkh s = + let from_alias s = Client_keys.Public_key_hash.find cctxt s in + let from_key s = + match Signature.Public_key_hash.of_b58check_opt s with + | None -> + failwith "Could not read public key hash for rollup node operator" + | Some pkh -> return pkh + in + Client_aliases.parse_alternatives + [("alias", from_alias); ("key", from_key)] + s + in + match String.split ~limit:1 ':' s with + | [_] -> + let+ pkh = parse_pkh s in + `Default pkh + | [purpose; operator_s] -> ( + match Configuration.purpose_of_string purpose with + | Some purpose -> + let+ pkh = parse_pkh operator_s in + `Purpose (purpose, pkh) + | None -> + let+ pkh = parse_pkh s in + `Default pkh) + | _ -> + (* cannot happen due to String.split's implementation. *) + assert false + +let possible_modes = List.map Configuration.string_of_mode Configuration.modes + +let mode_parameter = + Tezos_clic.parameter + ~autocomplete:(fun _ -> return possible_modes) + (fun _ m -> Lwt.return (Configuration.mode_of_string m)) + +let mode_doc = + Format.asprintf + "The mode for the rollup node (%s)@\n%a" + (String.concat ", " possible_modes) + (Format.pp_print_list (fun fmt mode -> + Format.fprintf + fmt + "- %s: %s" + (Configuration.string_of_mode mode) + (Configuration.description_of_mode mode))) + Configuration.modes + +let mode_param = Tezos_clic.param ~name:"mode" ~desc:mode_doc mode_parameter + +let mode_arg = + Tezos_clic.arg + ~long:"mode" + ~placeholder:"mode" + ~doc:(mode_doc ^ "\n(required when no configuration file exists)") + mode_parameter + +let rpc_addr_arg = + let default = Configuration.default_rpc_addr in + Tezos_clic.arg + ~long:"rpc-addr" + ~placeholder:"rpc-address|ip" + ~doc: + (Format.sprintf + "The address the smart rollup node listens to. Default value is %s" + default) + string_parameter + +let metrics_addr_arg = + Tezos_clic.arg + ~long:"metrics-addr" + ~placeholder: + "ADDR:PORT or :PORT (by default ADDR is localhost and PORT is 9933)" + ~doc:"The address of the smart rollup node metrics server." + string_parameter + +let dal_node_endpoint_arg = + Tezos_clic.arg + ~long:"dal-node" + ~placeholder:"dal-node-endpoint" + ~doc: + (Format.sprintf + "The address of the dal node from which the smart rollup node \ + downloads slots. When not provided, the rollup node will not support \ + the DAL. In production, a DAL node must be provided if DAL is \ + enabled and used in the rollup.") + (Tezos_clic.parameter (fun _ s -> Lwt.return_ok (Uri.of_string s))) + +let dac_observer_endpoint_arg = + Tezos_clic.arg + ~long:"dac-observer" + ~placeholder:"dac-observer-endpoint" + ~doc: + (Format.sprintf + "The address of the DAC observer node from which the smart rollup \ + node downloads preimages requested through the reveal channel.") + (Tezos_clic.parameter (fun _ s -> Lwt.return_ok (Uri.of_string s))) + +let dac_timeout_arg = + Tezos_clic.arg + ~long:"dac-timeout" + ~placeholder:"seconds" + ~doc: + "Timeout in seconds for which the DAC observer client will wait for a \ + preimage" + z_parameter + +let rpc_port_arg = + let default = Configuration.default_rpc_port |> string_of_int in + Tezos_clic.arg + ~long:"rpc-port" + ~placeholder:"rpc-port" + ~doc: + (Format.sprintf + "The port the smart rollup node listens to. Default value is %s" + default) + int_parameter + +let data_dir_arg = + let default = Configuration.default_data_dir in + Tezos_clic.default_arg + ~long:"data-dir" + ~placeholder:"data-dir" + ~doc: + (Format.sprintf + "The path to the smart rollup node data directory. Default value is %s" + default) + ~default + string_parameter + +let loser_mode_arg = + Tezos_clic.arg + ~long:"loser-mode" + ~placeholder:"mode" + ~doc:"Set the rollup node failure points (for test only!)." + (Tezos_clic.parameter (fun _ s -> + match Loser_mode.make s with + | Some t -> return t + | None -> failwith "Invalid syntax for failure points")) + +let reconnection_delay_arg = + let default = + Format.sprintf "%.1f" Configuration.default_reconnection_delay + in + let doc = + Format.asprintf + "The first reconnection delay, in seconds, to wait before reconnecting \ + to the Tezos node. The default delay is %s.\n\ + The actual delay varies to follow a randomized exponential backoff \ + (capped to 1.5h): [1.5^reconnection_attempt * delay ± 50%%]." + default + in + Tezos_clic.arg + ~long:"reconnection-delay" + ~placeholder:"delay" + ~doc + (Tezos_clic.parameter (fun _ p -> + try return (float_of_string p) with _ -> failwith "Cannot read float")) + +let injector_retention_period_arg = + Tezos_clic.arg + ~long:"injector-retention-period" + ~placeholder:"blocks" + ~doc: + (Format.sprintf + "The number of blocks the injector keeps in memory. Decrease to free \ + memory, and increase to be able to query information about included \ + messages for longer. Default value is %d" + Configuration.default_injector.retention_period) + @@ Tezos_clic.map_parameter int_parameter ~f:(fun p -> + if p > Configuration.max_injector_retention_period || p < 0 then + Format.ksprintf + Stdlib.failwith + "injector-retention-period should be a positive number smaller \ + than %d" + Configuration.max_injector_retention_period ; + p) + +let injector_attempts_arg = + Tezos_clic.arg + ~long:"injector-attempts" + ~placeholder:"number" + ~doc: + (Format.sprintf + "The number of attempts that the injector will make to inject an \ + operation when it fails. Default value is %d" + Configuration.default_injector.attempts) + @@ Tezos_clic.map_parameter int_parameter ~f:(fun p -> + if p < 0 then + Format.ksprintf + Stdlib.failwith + "injector-attempts should be positive" ; + p) + +let injection_ttl_arg = + Tezos_clic.arg + ~long:"injection-ttl" + ~placeholder:"number" + ~doc: + (Format.sprintf + "The number of blocks after which an operation that is injected but \ + never included is retried. Default value is %d" + Configuration.default_injector.injection_ttl) + @@ Tezos_clic.map_parameter int_parameter ~f:(fun p -> + if p < 1 then Stdlib.failwith "injection-ttl should be > 1" ; + p) + +let log_kernel_debug_arg = + Tezos_clic.switch + ~long:"log-kernel-debug" + ~doc:"Log the kernel debug output to kernel.log in the data directory" + () + +let log_kernel_debug_file_arg = + Tezos_clic.arg + ~long:"log-kernel-debug-file" + ~placeholder:"file" + ~doc:"" + string_parameter + +let group = + { + Tezos_clic.name = "sc_rollup.node"; + title = "Commands related to the smart rollup node."; + } + +let make_operators sc_rollup_node_operators = + let open Configuration in + let purposed_operators, default_operators = + List.partition_map + (function + | `Purpose p_operator -> Left p_operator + | `Default operator -> Right operator) + sc_rollup_node_operators + in + let default_operator = + match default_operators with + | [] -> None + | [default_operator] -> Some default_operator + | _ -> Stdlib.failwith "Multiple default operators" + in + make_purpose_map purposed_operators ~default:default_operator + +let configuration_from_args ~rpc_addr ~rpc_port ~metrics_addr ~loser_mode + ~reconnection_delay ~dal_node_endpoint ~dac_observer_endpoint ~dac_timeout + ~injector_retention_period ~injector_attempts ~injection_ttl ~mode + ~sc_rollup_address ~sc_rollup_node_operators ~log_kernel_debug = + let open Configuration in + let sc_rollup_node_operators = make_operators sc_rollup_node_operators in + let config = + { + sc_rollup_address; + sc_rollup_node_operators; + rpc_addr = Option.value ~default:default_rpc_addr rpc_addr; + rpc_port = Option.value ~default:default_rpc_port rpc_port; + reconnection_delay = + Option.value ~default:default_reconnection_delay reconnection_delay; + dal_node_endpoint; + dac_observer_endpoint; + dac_timeout; + metrics_addr; + fee_parameters = Operator_purpose_map.empty; + mode; + loser_mode = Option.value ~default:Loser_mode.no_failures loser_mode; + batcher = Configuration.default_batcher; + injector = + { + retention_period = + Option.value + ~default:default_injector.retention_period + injector_retention_period; + attempts = + Option.value ~default:default_injector.attempts injector_attempts; + injection_ttl = + Option.value ~default:default_injector.injection_ttl injection_ttl; + }; + l2_blocks_cache_size = Configuration.default_l2_blocks_cache_size; + log_kernel_debug; + } + in + check_mode config + +let patch_configuration_from_args configuration ~rpc_addr ~rpc_port + ~metrics_addr ~loser_mode ~reconnection_delay ~dal_node_endpoint + ~dac_observer_endpoint ~dac_timeout ~injector_retention_period + ~injector_attempts ~injection_ttl ~mode ~sc_rollup_address + ~sc_rollup_node_operators ~log_kernel_debug = + let open Configuration in + let new_sc_rollup_node_operators = make_operators sc_rollup_node_operators in + (* Merge operators *) + let sc_rollup_node_operators = + Operator_purpose_map.merge + (fun _purpose new_operator _old_operator -> new_operator) + new_sc_rollup_node_operators + configuration.sc_rollup_node_operators + in + let configuration = + Configuration. + { + configuration with + sc_rollup_address = + Option.value + ~default:configuration.sc_rollup_address + sc_rollup_address; + sc_rollup_node_operators; + mode = Option.value ~default:configuration.mode mode; + rpc_addr = Option.value ~default:configuration.rpc_addr rpc_addr; + rpc_port = Option.value ~default:configuration.rpc_port rpc_port; + dal_node_endpoint = + Option.either dal_node_endpoint configuration.dal_node_endpoint; + dac_observer_endpoint = + Option.either + dac_observer_endpoint + configuration.dac_observer_endpoint; + dac_timeout = Option.either dac_timeout configuration.dac_timeout; + reconnection_delay = + Option.value + ~default:configuration.reconnection_delay + reconnection_delay; + injector = + { + retention_period = + Option.value + ~default:default_injector.retention_period + injector_retention_period; + attempts = + Option.value ~default:default_injector.attempts injector_attempts; + injection_ttl = + Option.value ~default:default_injector.injection_ttl injection_ttl; + }; + loser_mode = Option.value ~default:configuration.loser_mode loser_mode; + metrics_addr = Option.either metrics_addr configuration.metrics_addr; + log_kernel_debug = log_kernel_debug || configuration.log_kernel_debug; + } + in + Configuration.check_mode configuration + +let create_or_read_config ~data_dir ~rpc_addr ~rpc_port ~metrics_addr + ~loser_mode ~reconnection_delay ~dal_node_endpoint ~dac_observer_endpoint + ~dac_timeout ~injector_retention_period ~injector_attempts ~injection_ttl + ~mode ~sc_rollup_address ~sc_rollup_node_operators ~log_kernel_debug = + let open Lwt_result_syntax in + let config_file = Configuration.config_filename ~data_dir in + let*! exists_config = Lwt_unix.file_exists config_file in + if exists_config then + (* Read configuration from file and patch if user wanted to override + some fields with values provided by arguments. *) + let* configuration = Configuration.load ~data_dir in + let*? configuration = + patch_configuration_from_args + configuration + ~rpc_addr + ~rpc_port + ~metrics_addr + ~loser_mode + ~reconnection_delay + ~dal_node_endpoint + ~dac_observer_endpoint + ~dac_timeout + ~injector_retention_period + ~injector_attempts + ~injection_ttl + ~mode + ~sc_rollup_address + ~sc_rollup_node_operators + ~log_kernel_debug + in + return configuration + else + (* Build configuration from arguments only. *) + let*? mode = + Option.value_e + mode + ~error: + (TzTrace.make + @@ error_of_fmt + "Argument --mode is required when configuration file is not \ + present.") + in + let*? sc_rollup_address = + Option.value_e + sc_rollup_address + ~error: + (TzTrace.make + @@ error_of_fmt + "Argument --rollup is required when configuration file is not \ + present.") + in + let*? config = + configuration_from_args + ~rpc_addr + ~rpc_port + ~metrics_addr + ~loser_mode + ~reconnection_delay + ~dal_node_endpoint + ~dac_observer_endpoint + ~dac_timeout + ~injector_retention_period + ~injector_attempts + ~injection_ttl + ~mode + ~sc_rollup_address + ~sc_rollup_node_operators + ~log_kernel_debug + in + return config + +let config_init_command = + let open Lwt_result_syntax in + let open Tezos_clic in + command + ~group + ~desc:"Configure the smart rollup node." + (args14 + force_switch + data_dir_arg + rpc_addr_arg + rpc_port_arg + metrics_addr_arg + loser_mode_arg + reconnection_delay_arg + dal_node_endpoint_arg + dac_observer_endpoint_arg + dac_timeout_arg + injector_retention_period_arg + injector_attempts_arg + injection_ttl_arg + log_kernel_debug_arg) + (prefix "init" @@ mode_param + @@ prefixes ["config"; "for"] + @@ sc_rollup_address_param + @@ prefixes ["with"; "operators"] + @@ seq_of_param @@ sc_rollup_node_operator_param) + (fun ( force, + data_dir, + rpc_addr, + rpc_port, + metrics_addr, + loser_mode, + reconnection_delay, + dal_node_endpoint, + dac_observer_endpoint, + dac_timeout, + injector_retention_period, + injector_attempts, + injection_ttl, + log_kernel_debug ) + mode + sc_rollup_address + sc_rollup_node_operators + cctxt -> + let*? config = + configuration_from_args + ~rpc_addr + ~rpc_port + ~metrics_addr + ~loser_mode + ~reconnection_delay + ~dal_node_endpoint + ~dac_observer_endpoint + ~dac_timeout + ~injector_retention_period + ~injector_attempts + ~injection_ttl + ~mode + ~sc_rollup_address + ~sc_rollup_node_operators + ~log_kernel_debug + in + let* () = Configuration.save ~force ~data_dir config in + let*! () = + cctxt#message + "Smart rollup node configuration written in %s" + (Configuration.config_filename ~data_dir) + in + return_unit) + +let legacy_run_command = + let open Tezos_clic in + let open Lwt_result_syntax in + command + ~group + ~desc:"Run the rollup node daemon (deprecated)." + (args16 + data_dir_arg + mode_arg + sc_rollup_address_arg + rpc_addr_arg + rpc_port_arg + metrics_addr_arg + loser_mode_arg + reconnection_delay_arg + dal_node_endpoint_arg + dac_observer_endpoint_arg + dac_timeout_arg + injector_retention_period_arg + injector_attempts_arg + injection_ttl_arg + log_kernel_debug_arg + log_kernel_debug_file_arg) + (prefixes ["run"] @@ stop) + (fun ( data_dir, + mode, + sc_rollup_address, + rpc_addr, + rpc_port, + metrics_addr, + loser_mode, + reconnection_delay, + dal_node_endpoint, + dac_observer_endpoint, + dac_timeout, + injector_retention_period, + injector_attempts, + injection_ttl, + log_kernel_debug, + log_kernel_debug_file ) + cctxt -> + let* configuration = + create_or_read_config + ~data_dir + ~rpc_addr + ~rpc_port + ~metrics_addr + ~loser_mode + ~reconnection_delay + ~dal_node_endpoint + ~dac_observer_endpoint + ~dac_timeout + ~injector_retention_period + ~injector_attempts + ~injection_ttl + ~mode + ~sc_rollup_address + ~sc_rollup_node_operators:[] + ~log_kernel_debug + in + Node_daemon.run ~data_dir ?log_kernel_debug_file configuration cctxt) + +let run_command = + let open Tezos_clic in + let open Lwt_result_syntax in + command + ~group + ~desc: + "Run the rollup node daemon. Arguments overwrite values provided in the \ + configuration file." + (args14 + data_dir_arg + rpc_addr_arg + rpc_port_arg + metrics_addr_arg + loser_mode_arg + reconnection_delay_arg + dal_node_endpoint_arg + dac_observer_endpoint_arg + dac_timeout_arg + injector_retention_period_arg + injector_attempts_arg + injection_ttl_arg + log_kernel_debug_arg + log_kernel_debug_file_arg) + (prefixes ["run"] @@ mode_param @@ prefixes ["for"] + @@ sc_rollup_address_param + @@ prefixes ["with"; "operators"] + @@ seq_of_param @@ sc_rollup_node_operator_param) + (fun ( data_dir, + rpc_addr, + rpc_port, + metrics_addr, + loser_mode, + reconnection_delay, + dal_node_endpoint, + dac_observer_endpoint, + dac_timeout, + injector_retention_period, + injector_attempts, + injection_ttl, + log_kernel_debug, + log_kernel_debug_file ) + mode + sc_rollup_address + sc_rollup_node_operators + cctxt -> + let* configuration = + create_or_read_config + ~data_dir + ~rpc_addr + ~rpc_port + ~metrics_addr + ~loser_mode + ~reconnection_delay + ~dal_node_endpoint + ~dac_observer_endpoint + ~dac_timeout + ~injector_retention_period + ~injector_attempts + ~injection_ttl + ~mode:(Some mode) + ~sc_rollup_address:(Some sc_rollup_address) + ~sc_rollup_node_operators + ~log_kernel_debug + in + Node_daemon.run ~data_dir ?log_kernel_debug_file configuration cctxt) + +let protocols_command = + let open Tezos_clic in + let open Lwt_result_syntax in + command + ~group + ~desc:"Shows the protocols supported by this rollup node." + no_options + (prefixes ["show"; "supported"; "protocols"] @@ stop) + (fun () (cctxt : #Client_context.full) -> + let protocols = Protocol_daemons.registered_protocols () in + let*! () = + match protocols with + | [] -> cctxt#error "No protocols supported by rollup node!" + | _ -> + cctxt#message + "@[%a@]" + (Format.pp_print_list Protocol_hash.pp) + protocols + in + return_unit) + +(** Command to dump the rollup node metrics. *) +let dump_metrics = + let open Tezos_clic in + let open Lwt_result_syntax in + command + ~group + ~desc:"dump the rollup node available metrics in CSV format." + no_options + (prefixes ["dump-metrics"] @@ stop) + (fun () (cctxt : #Client_context.full) -> + let*! metrics = + Prometheus.CollectorRegistry.collect Metrics.sc_rollup_node_registry + in + let*! () = cctxt#message "%a@." Metrics.print_csv_metrics metrics in + return_unit) + +let sc_rollup_commands () = + [ + config_init_command; + run_command; + legacy_run_command; + protocols_command; + dump_metrics; + ] + +let select_commands _ctxt _ = + Lwt_result_syntax.return + (sc_rollup_commands () @ Client_helpers_commands.commands ()) + +let () = Client_main_run.run (module Daemon_config) ~select_commands diff --git a/src/lib_crawler/layer_1.ml b/src/lib_crawler/layer_1.ml index 2431fc7d5eb08a2057457f78925e74bdd7e93af9..dc39d6d020b73d76514a7f9fe441d83e8edca2c7 100644 --- a/src/lib_crawler/layer_1.ml +++ b/src/lib_crawler/layer_1.ml @@ -172,6 +172,8 @@ let iter_heads l1_ctxt f = (fun () -> Lwt.no_cancel @@ loop l1_ctxt) (function Iter_error e -> Lwt.return_error e | exn -> fail (Exn exn)) +let first l1_ctxt = Lwt_stream.peek l1_ctxt.heads + (** [predecessors_of_blocks hashes] given a list of successive block hashes, from newest to oldest, returns an associative list that associates a hash to its predecessor in this list. *) diff --git a/src/lib_crawler/layer_1.mli b/src/lib_crawler/layer_1.mli index 8477675383406f966c36cb0d8219d9889a64e967..a064ca4de2f28eeabfe49748afd4f3c039c7d8c6 100644 --- a/src/lib_crawler/layer_1.mli +++ b/src/lib_crawler/layer_1.mli @@ -57,6 +57,9 @@ val iter_heads : (Block_hash.t * Block_header.t -> unit tzresult Lwt.t) -> unit tzresult Lwt.t +(** [first t] returns the first block of the stream if any. *) +val first : t -> (Block_hash.t * Block_header.t) option Lwt.t + (** {2 Helper functions for the Layer 1 chain} *) (** [get_predecessor_opt state head] returns the predecessor of block [head], diff --git a/src/lib_sc_rollup_node/dune b/src/lib_sc_rollup_node/dune index c161b08d5786f98da70ef4b354694faedb470183..741f0f0ec62d79db4cdeabbf38b5c1029838459f 100644 --- a/src/lib_sc_rollup_node/dune +++ b/src/lib_sc_rollup_node/dune @@ -13,6 +13,10 @@ cohttp-lwt-unix octez-node-config prometheus-app + tezos-client-base + tezos-client-base-unix + tezos-rpc-http-server + tezos_layer2_store octez-crawler octez-injector octez-sc-rollup) @@ -23,6 +27,9 @@ -open Tezos_base -open Tezos_stdlib_unix -open Tezos_crypto + -open Tezos_client_base + -open Tezos_client_base_unix + -open Tezos_layer2_store -open Octez_crawler -open Octez_injector -open Octez_sc_rollup)) diff --git a/src/lib_sc_rollup_node/node_daemon.ml b/src/lib_sc_rollup_node/node_daemon.ml new file mode 100644 index 0000000000000000000000000000000000000000..1d4526a00e8b95efad9388cc39cd700757de3340 --- /dev/null +++ b/src/lib_sc_rollup_node/node_daemon.ml @@ -0,0 +1,389 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Functori, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +module type PROTO_CONTEXT = sig + module Daemon : Protocol_daemon_sig.S + + val node_ctxt : Daemon.Node_context.rw +end + +type state = { + data_dir : string; + log_kernel_debug_file : string option; + cctxt : Client_context.full; + l1 : Layer_1.t; + configuration : Configuration.t; + mutable protocol : Protocol_hash.t; + mutable proto_level : int; + mutable proto_ctxt : (module PROTO_CONTEXT); + mutable rpc_server : Tezos_rpc_http_server.RPC_server.server; +} + +module Layer1 = struct + module Blocks_cache = + Aches_lwt.Lache.Make_option + (Aches.Rache.Transfer (Aches.Rache.LRU) (Block_hash)) + + type headers_cache = Block_header.shell_header Blocks_cache.t + + (** Global block headers cache for the smart rollup node. *) + let headers_cache : headers_cache = Blocks_cache.create 32 + + include Octez_crawler.Layer_1 + + let cache_shell_header hash header = + Blocks_cache.put headers_cache hash (Lwt.return_some header) + + let iter_heads l1_ctxt f = + iter_heads l1_ctxt @@ fun (hash, {shell; _}) -> + cache_shell_header hash shell ; + f (hash, shell) + + let first l1_ctxt = + let open Lwt_option_syntax in + let+ hash, {shell; _} = first l1_ctxt in + cache_shell_header hash shell ; + (hash, shell) + + let get_predecessor state (hash, level) = + let open Lwt_result_syntax in + let open (val state.proto_ctxt) in + let open Daemon in + let* pred = Node_context.get_l2_predecessor node_ctxt hash in + match pred with + | Some p -> return p + | None -> + (* [head] is not already known in the L2 chain *) + get_predecessor state.l1 (hash, level) + + let get_tezos_reorg_for_new_head state old_head new_head = + let open Lwt_result_syntax in + let get_reorg = + get_tezos_reorg_for_new_head + state.l1 + ~get_old_predecessor:(get_predecessor state) + in + let*! reorg = get_reorg old_head new_head in + match reorg with + | Error trace + when TzTrace.fold + (fun yes error -> + yes + || + match error with + | Octez_crawler.Layer_1.Cannot_find_predecessor _ -> true + | _ -> false) + false + trace -> + (* The reorganization could not be computed entirely because of missing + info on the Layer 1. We fallback to just the new chain as there is no + real treatment for rolledback blocks so far. *) + let old_head = + match old_head with `Head (_, l) | `Level l -> `Level l + in + get_reorg old_head new_head + | reorg -> Lwt.return reorg + + (** [fetch_tezos_shell_header cctxt hash] returns a block shell header of + [hash]. Looks for the block in the blocks cache first, and fetches it from + the L1 node otherwise. *) + let fetch_tezos_shell_header cctxt hash = + let open Lwt_syntax in + let errors = ref None in + let fetch hash = + let* shell_header = + Tezos_shell_services.Shell_services.Blocks.Header.shell_header + cctxt + ~chain:`Main + ~block:(`Hash (hash, 0)) + () + in + match shell_header with + | Error errs -> + errors := Some errs ; + return_none + | Ok shell_header -> return_some shell_header + in + let+ shell_header = + Blocks_cache.bind_or_put headers_cache hash fetch Lwt.return + in + match (shell_header, !errors) with + | None, None -> + (* This should not happen if {!find_in_cache} behaves correctly, + i.e. calls {!fetch} for cache misses. *) + error_with + "Fetching Tezos block %a failed unexpectedly" + Block_hash.pp + hash + | None, Some errs -> Error errs + | Some shell_header, _ -> Ok shell_header +end + +let stop_daemon state = + let open Lwt_result_syntax in + let open (val state.proto_ctxt) in + let*! () = Tezos_rpc_http_server.RPC_server.shutdown state.rpc_server in + let* () = Daemon.stop_workers node_ctxt in + let* () = Daemon.Node_context.close node_ctxt in + return_unit + +let initial_state_of_head ~data_dir ?log_kernel_debug_file cctxt l1 + configuration (hash, header) = + let open Lwt_result_syntax in + let* {current_protocol; _} = + Tezos_shell_services.Shell_services.Blocks.protocols + cctxt + ~chain:cctxt#chain + ~block:(`Hash (hash, 0)) + () + in + let*? proto_daemon = + Protocol_daemons.proto_daemon_for_protocol current_protocol + in + let module Current_proto_daemon = (val proto_daemon) in + let* node_ctxt = + Current_proto_daemon.Node_context.init + cctxt + ~head:hash + ~data_dir + ?log_kernel_debug_file + Read_write + configuration + in + let* () = Current_proto_daemon.start_workers configuration node_ctxt in + let rpc_server = + Current_proto_daemon.RPC_server.init configuration node_ctxt + in + let*! () = + let open Tezos_rpc_http_server in + let Configuration.{rpc_addr; rpc_port; _} = configuration in + let rpc_addr = P2p_addr.of_string_exn rpc_addr in + let host = Ipaddr.V6.to_string rpc_addr in + let node = `TCP (`Port rpc_port) in + RPC_server.launch + ~host + rpc_server + ~callback:(RPC_server.resto_callback rpc_server) + node + in + let proto_level = header.Block_header.proto_level in + let module Proto_context = struct + module Daemon = Current_proto_daemon + + let node_ctxt = node_ctxt + end in + return + { + data_dir; + log_kernel_debug_file; + cctxt = (cctxt :> Client_context.full); + l1; + configuration; + protocol = current_protocol; + proto_level; + proto_ctxt = (module Proto_context); + rpc_server; + } + +let protocol_migration + ({data_dir; log_kernel_debug_file; cctxt; l1; configuration; _} as state) + (hash, header) = + let open Lwt_result_syntax in + (* Stop the daemon for the previous protocol *) + let* () = stop_daemon state in + Format.eprintf + "Migrating from %d to %d@." + state.proto_level + header.Block_header.proto_level ; + (* And start the one for the current protocol *) + let* new_state = + initial_state_of_head + ~data_dir + ?log_kernel_debug_file + cctxt + l1 + configuration + (hash, header) + in + state.protocol <- new_state.protocol ; + state.proto_level <- new_state.proto_level ; + state.rpc_server <- new_state.rpc_server ; + state.proto_ctxt <- new_state.proto_ctxt ; + return_unit + +let process_head state ((_hash, header) as head) = + let open Lwt_result_syntax in + let* () = + when_ (state.proto_level <> header.Block_header.proto_level) @@ fun () -> + protocol_migration state head + in + let open (val state.proto_ctxt) in + Daemon.process_block node_ctxt head + +let on_layer_1_head state ((hash, header) as head) = + let open Lwt_result_syntax in + let open (val state.proto_ctxt) in + let* old_head = Daemon.Node_context.last_processed_block node_ctxt in + let old_head = + match old_head with + | Some (hash, level) -> `Head (hash, level) + | None -> + (* if no head has been processed yet, we want to handle all blocks + since, and including, the rollup origination. *) + let origination_level = + Daemon.Node_context.origination_level node_ctxt + in + `Level (Int32.pred origination_level) + in + let stripped_head = (hash, header.Block_header.level) in + let* reorg = + Layer1.get_tezos_reorg_for_new_head state old_head stripped_head + in + let get_header block_hash = + if Block_hash.equal block_hash hash then return head + else + let+ header = Layer1.fetch_tezos_shell_header state.cctxt block_hash in + (block_hash, header) + in + let*! () = Node_daemon_event.processing_heads_iteration reorg.new_chain in + let* () = + List.iter_es + (fun (block, _level) -> + let* header = get_header block in + process_head state header) + reorg.new_chain + in + let* () = Daemon.on_layer_1_head_extra node_ctxt head in + let*! () = Node_daemon_event.new_heads_processed reorg.new_chain in + return_unit + +let daemonize state = Layer1.iter_heads state.l1 (on_layer_1_head state) + +let degraded_mode_on_block state ((_hash, header) as head) = + let open Lwt_result_syntax in + let* () = + when_ (state.proto_level <> header.Block_header.proto_level) @@ fun () -> + protocol_migration state head + in + let open (val state.proto_ctxt) in + Daemon.degraded_mode_on_block head + +let degraded_refutation_mode state = + let open Lwt_result_syntax in + let open (val state.proto_ctxt) in + let* () = Daemon.enter_degraded_mode node_ctxt in + Layer1.iter_heads state.l1 (degraded_mode_on_block state) + +let install_finalizer state = + let open Lwt_syntax in + Lwt_exit.register_clean_up_callback ~loc:__LOC__ @@ fun _exit_status -> + let open (val state.proto_ctxt) in + let message = state.cctxt#message in + let* () = message "Shutting down RPC server@." in + let* () = Tezos_rpc_http_server.RPC_server.shutdown state.rpc_server in + let* () = message "Shutting down workers@." in + let* (_ : unit tzresult) = Daemon.stop_workers node_ctxt in + let* (_ : unit tzresult) = Daemon.Node_context.close node_ctxt in + Tezos_base_unix.Internal_event_unix.close () + +let start_metrics_server (configuration : Configuration.t) = + Lwt.dont_wait + (fun () -> + let open Lwt_syntax in + let* r = Metrics.metrics_serve configuration.metrics_addr in + match r with + | Ok () -> Lwt.return_unit + | Error err -> + Node_events.(metrics_ended (Format.asprintf "%a" pp_print_trace err))) + (fun exn -> Node_events.(metrics_ended_dont_wait (Printexc.to_string exn))) + +let error_is_lost_game err = + let has_suffix ~suffix s = + let x = String.length suffix in + let n = String.length s in + n >= x && String.sub s (n - x) x = suffix + in + let err_id_str = + let open Option_syntax in + let err_json = + Data_encoding.Json.construct Error_monad.error_encoding err + in + let* err_id = Ezjsonm.find_opt err_json ["id"] in + Ezjsonm.decode_string err_id + in + match err_id_str with + | None -> false + | Some err_id_str -> has_suffix err_id_str ~suffix:"sc_rollup.node.lost_game" + +let run ~data_dir ?log_kernel_debug_file (configuration : Configuration.t) + (cctxt : #Client_context.full) = + let open Lwt_result_syntax in + Random.self_init () (* Initialize random state (for reconnection delays) *) ; + let open Configuration in + let* () = + (* Check that the operators are valid keys. *) + Operator_purpose_map.iter_es + (fun _purpose operator -> + let+ _pkh, _pk, _skh = Client_keys.get_key cctxt operator in + ()) + configuration.sc_rollup_node_operators + in + let* l1 = + Layer1.start + ~name:"sc_rollup_node" + ~reconnection_delay:configuration.reconnection_delay + cctxt + in + let*! head = Layer1.first l1 in + let*? head = + match head with + | None -> error_with "Could not obtain head from stream" + | Some h -> ok h + in + let* state = + initial_state_of_head + ~data_dir + ?log_kernel_debug_file + cctxt + l1 + configuration + head + in + let (_ : Lwt_exit.clean_up_callback_id) = install_finalizer state in + start_metrics_server configuration ; + let*! () = + Node_daemon_event.node_is_ready + ~rpc_addr:configuration.rpc_addr + ~rpc_port:configuration.rpc_port + in + protect ~on_error:(fun e -> + if List.exists error_is_lost_game e then ( + Format.eprintf "%!%a@.Exiting.@." pp_print_trace e ; + let*! _ = Lwt_exit.exit_and_wait 1 in + return_unit) + else + let*! () = Node_daemon_event.error e in + degraded_refutation_mode state) + @@ fun () -> daemonize state diff --git a/src/lib_sc_rollup_node/node_daemon_event.ml b/src/lib_sc_rollup_node/node_daemon_event.ml new file mode 100644 index 0000000000000000000000000000000000000000..f630517ab236851e17edb50b690c94ff6fa99dde --- /dev/null +++ b/src/lib_sc_rollup_node/node_daemon_event.ml @@ -0,0 +1,95 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 TriliTech *) +(* Copyright (c) 2023 Functori, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +module Simple = struct + include Internal_event.Simple + + let section = ["sc_rollup_node"] + + let node_is_ready = + declare_2 + ~section + ~name:"smart_rollup_node_is_ready" + ~msg:"The smart rollup node is listening to {addr}:{port}" + ~level:Notice + ("addr", Data_encoding.string) + ("port", Data_encoding.uint16) + + let section = section @ ["daemon"] + + let processing_heads_iteration = + declare_3 + ~section + ~name:"sc_rollup_daemon_processing_heads" + ~msg: + "A new iteration of process_heads has been triggered: processing \ + {number} heads from level {from} to level {to}" + ~level:Notice + ("number", Data_encoding.int31) + ("from", Data_encoding.int32) + ("to", Data_encoding.int32) + + let new_heads_processed = + declare_3 + ~section + ~name:"sc_rollup_node_layer_1_new_heads_processed" + ~msg: + "Finished processing {number} layer 1 heads for levels {from} to {to}" + ~level:Notice + ("number", Data_encoding.int31) + ("from", Data_encoding.int32) + ("to", Data_encoding.int32) + + let error = + declare_1 + ~section + ~name:"sc_rollup_daemon_error" + ~msg:"Fatal daemon error: {error}" + ~level:Fatal + ("error", trace_encoding) + ~pp1:pp_print_trace +end + +let node_is_ready ~rpc_addr ~rpc_port = + Simple.(emit node_is_ready (rpc_addr, rpc_port)) + +let new_heads_iteration event = function + | oldest :: rest -> + let newest = + match List.rev rest with [] -> oldest | newest :: _ -> newest + in + let number = + Int32.sub (snd newest) (snd oldest) |> Int32.succ |> Int32.to_int + in + Simple.emit event (number, snd oldest, snd newest) + | [] -> Lwt.return_unit + +let processing_heads_iteration l = + new_heads_iteration Simple.processing_heads_iteration l + +let new_heads_processed l = new_heads_iteration Simple.new_heads_processed l + +let error e = Simple.(emit error) e diff --git a/src/lib_sc_rollup_node/node_daemon_event.mli b/src/lib_sc_rollup_node/node_daemon_event.mli new file mode 100644 index 0000000000000000000000000000000000000000..d93787cdd8a5e63ece1809d4beaeceba44c5675d --- /dev/null +++ b/src/lib_sc_rollup_node/node_daemon_event.mli @@ -0,0 +1,41 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* Copyright (c) 2023 Functori, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +(** This module defines functions that emit the events used by the smart + rollup node daemon (see {!Node_daemon}). *) + +val node_is_ready : rpc_addr:string -> rpc_port:int -> unit Lwt.t + +(** [processing_heads_iteration heads] emits the event that the [heads] are + going to be processed. *) +val processing_heads_iteration : (_ * int32) list -> unit Lwt.t + +(** [new_heads_processed heads] emits the event that the [heads] were + processed. *) +val new_heads_processed : (_ * int32) list -> unit Lwt.t + +(** Emit a fatal error for the daemon. *) +val error : tztrace -> unit Lwt.t diff --git a/src/lib_sc_rollup_node/protocol_daemon_sig.ml b/src/lib_sc_rollup_node/protocol_daemon_sig.ml new file mode 100644 index 0000000000000000000000000000000000000000..b31dac8ea54627febdb822c569e8674318f46468 --- /dev/null +++ b/src/lib_sc_rollup_node/protocol_daemon_sig.ml @@ -0,0 +1,84 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Functori, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +module type CONTEXT = sig + type 'a t constraint 'a = [< `Read | `Write > `Read] + + (** Read/write node context {!t}. *) + type rw = [`Read | `Write] t + + (** Read only node context {!t}. *) + type ro = [`Read] t + + val init : + #Client_context.full -> + ?head:Block_hash.t -> + data_dir:string -> + ?log_kernel_debug_file:string -> + 'a Store_sigs.mode -> + Configuration.t -> + 'a t tzresult Lwt.t + + val get_l2_predecessor : + _ t -> Block_hash.t -> ((Block_hash.t * int32) option, tztrace) result Lwt.t + + val last_processed_block : _ t -> (Block_hash.t * int32) option tzresult Lwt.t + + val origination_level : _ t -> int32 + + val close : _ t -> unit tzresult Lwt.t +end + +module type RPC_SERVER = sig + type context + + val init : + Configuration.t -> context -> Tezos_rpc_http_server.RPC_server.server +end + +module type S = sig + module Node_context : CONTEXT + + module RPC_server : RPC_SERVER with type context := Node_context.rw + + val process_block : + Node_context.rw -> + Block_hash.t * Block_header.shell_header -> + unit tzresult Lwt.t + + val on_layer_1_head_extra : + Node_context.rw -> + Block_hash.t * Block_header.shell_header -> + unit tzresult Lwt.t + + val enter_degraded_mode : Node_context.rw -> unit tzresult Lwt.t + + val degraded_mode_on_block : + Block_hash.t * Block_header.shell_header -> unit tzresult Lwt.t + + val start_workers : Configuration.t -> Node_context.rw -> unit tzresult Lwt.t + + val stop_workers : _ Node_context.t -> unit tzresult Lwt.t +end diff --git a/src/lib_sc_rollup_node/protocol_daemons.ml b/src/lib_sc_rollup_node/protocol_daemons.ml new file mode 100644 index 0000000000000000000000000000000000000000..e836ee21183162638d2519f163d789cc76db45bd --- /dev/null +++ b/src/lib_sc_rollup_node/protocol_daemons.ml @@ -0,0 +1,57 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Functori, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +type error += Unsupported_protocol of Protocol_hash.t + +let () = + register_error_kind + ~id:"sc_rollup.node.unsupported_protocol" + ~title:"Protocol not supported by rollup node" + ~description:"Protocol not supported by rollup node." + ~pp:(fun ppf proto -> + Format.fprintf + ppf + "Protocol %a is not supported by the rollup node." + Protocol_hash.pp + proto) + `Permanent + Data_encoding.(obj1 (req "protocol" Protocol_hash.encoding)) + (function Unsupported_protocol p -> Some p | _ -> None) + (fun p -> Unsupported_protocol p) + +type proto_daemon = (module Protocol_daemon_sig.S) + +let proto_daemons : proto_daemon Protocol_hash.Table.t = + Protocol_hash.Table.create 7 + +let register protocol daemon = + Protocol_hash.Table.replace proto_daemons protocol daemon + +let proto_daemon_for_protocol protocol = + Protocol_hash.Table.find proto_daemons protocol + |> Option.to_result ~none:[Unsupported_protocol protocol] + +let registered_protocols () = + Protocol_hash.Table.to_seq_keys proto_daemons |> List.of_seq diff --git a/src/lib_sc_rollup_node/protocol_daemons.mli b/src/lib_sc_rollup_node/protocol_daemons.mli new file mode 100644 index 0000000000000000000000000000000000000000..493abb27521afb5f5b929ff8405200b63bf8199c --- /dev/null +++ b/src/lib_sc_rollup_node/protocol_daemons.mli @@ -0,0 +1,37 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Functori, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +type proto_daemon = (module Protocol_daemon_sig.S) + +(** Register a protocol daemon for a specific protocol to be used by the + rollup node. *) +val register : Protocol_hash.t -> proto_daemon -> unit + +(** Return the protocol daemon for a given protocol (or an error if not + supported). *) +val proto_daemon_for_protocol : Protocol_hash.t -> proto_daemon tzresult + +(** Returns the list of registered protocols. *) +val registered_protocols : unit -> Protocol_hash.t list diff --git a/src/proto_alpha/lib_sc_rollup_node/RPC_server.ml b/src/proto_alpha/lib_sc_rollup_node/RPC_server.ml index ad948a1a6d6a6d94e3eb3343c01829f135e7507d..8177a8ff9805fd0ee9762860f61bbd224a68030a 100644 --- a/src/proto_alpha/lib_sc_rollup_node/RPC_server.ml +++ b/src/proto_alpha/lib_sc_rollup_node/RPC_server.ml @@ -504,17 +504,18 @@ let register (node_ctxt : _ Node_context.t) = PVM.build_directory; ] +let init _configuration node_ctxt = + let acl = RPC_server.Acl.allow_all in + let dir = register node_ctxt in + RPC_server.init_server dir ~acl ~media_types:Media_type.all_media_types + let start node_ctxt configuration = let open Lwt_result_syntax in let Configuration.{rpc_addr; rpc_port; _} = configuration in let rpc_addr = P2p_addr.of_string_exn rpc_addr in let host = Ipaddr.V6.to_string rpc_addr in let node = `TCP (`Port rpc_port) in - let acl = RPC_server.Acl.allow_all in - let dir = register node_ctxt in - let server = - RPC_server.init_server dir ~acl ~media_types:Media_type.all_media_types - in + let server = init configuration node_ctxt in protect @@ fun () -> let*! () = RPC_server.launch diff --git a/src/proto_alpha/lib_sc_rollup_node/RPC_server.mli b/src/proto_alpha/lib_sc_rollup_node/RPC_server.mli index 7830dad2505f33a4f67b31086d368c8c26e75514..b0120ded5e5822ce1c2fdd5e55321c94bd6630c0 100644 --- a/src/proto_alpha/lib_sc_rollup_node/RPC_server.mli +++ b/src/proto_alpha/lib_sc_rollup_node/RPC_server.mli @@ -25,6 +25,10 @@ open Tezos_rpc_http_server +(** [init config node_ctxt] creates an RPC server with the directory but does + not start yet. *) +val init : Configuration.t -> Node_context.rw -> RPC_server.server + (** [start node_ctxt config] starts an RPC server listening for requests on the port [config.rpc_port] and address [config.rpc_addr]. *) val start : diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon.ml b/src/proto_alpha/lib_sc_rollup_node/daemon.ml index 4289f641b556b0653714575981e4e8fbf4a12dce..afa7cee2df99d05eb822d3d591ddb41b07b24dd5 100644 --- a/src/proto_alpha/lib_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/lib_sc_rollup_node/daemon.ml @@ -222,7 +222,7 @@ let process_finalized_l1_operation (type kind) _node_ctxt _head ~source:_ (_result : kind successful_manager_operation_result) = return_unit -let process_l1_operation (type kind) ~finalized node_ctxt head ~source +let process_l1_operation (type kind) node_ctxt head ~source (operation : kind manager_operation) (result : kind Apply_results.manager_operation_result) = let open Lwt_result_syntax in @@ -245,7 +245,7 @@ let process_l1_operation (type kind) ~finalized node_ctxt head ~source if not (is_for_my_rollup operation) then return_unit else (* Only look at operations that are for the node's rollup *) - let*! () = Daemon_event.included_operation ~finalized operation result in + let*! () = Daemon_event.included_operation operation result in match result with | Applied success_result -> process_included_l1_operation @@ -258,15 +258,14 @@ let process_l1_operation (type kind) ~finalized node_ctxt head ~source (* No action for non successful operations *) return_unit -let process_l1_block_operations ~finalized node_ctxt (Layer1.{hash; _} as head) - = +let process_l1_block_operations node_ctxt (Layer1.{hash; _} as head) = let open Lwt_result_syntax in let* block = Layer1.fetch_tezos_block node_ctxt.Node_context.cctxt hash in let apply (type kind) accu ~source (operation : kind manager_operation) result = let open Lwt_result_syntax in let* () = accu in - process_l1_operation ~finalized node_ctxt head ~source operation result + process_l1_operation node_ctxt head ~source operation result in let apply_internal (type kind) accu ~source:_ (_operation : kind Apply_internal_results.internal_operation) @@ -285,23 +284,6 @@ let before_origination (node_ctxt : _ Node_context.t) Layer1.{level; _} = let origination_level = Raw_level.to_int32 node_ctxt.genesis_info.level in level < origination_level -let rec processed_finalized_block (node_ctxt : _ Node_context.t) - Layer1.({hash; level} as block) = - let open Lwt_result_syntax in - let* last_finalized = Node_context.get_finalized_head_opt node_ctxt in - let already_finalized = - match last_finalized with - | Some finalized -> level <= Raw_level.to_int32 finalized.header.level - | None -> false - in - unless (already_finalized || before_origination node_ctxt block) @@ fun () -> - let* predecessor = Node_context.get_predecessor_opt node_ctxt block in - let* () = Option.iter_es (processed_finalized_block node_ctxt) predecessor in - let*! () = Daemon_event.head_processing hash level ~finalized:true in - let* () = process_l1_block_operations ~finalized:true node_ctxt block in - let* () = Node_context.mark_finalized_head node_ctxt hash in - return_unit - let previous_context (node_ctxt : _ Node_context.t) ~predecessor Layer1.{hash = _; level} = let open Lwt_result_syntax in @@ -311,12 +293,12 @@ let previous_context (node_ctxt : _ Node_context.t) ~predecessor return (Context.empty node_ctxt.context) else Node_context.checkout_context node_ctxt predecessor.Layer1.hash -let rec process_head (node_ctxt : _ Node_context.t) +let process_block (node_ctxt : _ Node_context.t) + ?(process_predecessor = fun _ _ -> return_unit) Layer1.({hash; level} as head) = let open Lwt_result_syntax in let* already_processed = Node_context.is_processed node_ctxt hash in unless (already_processed || before_origination node_ctxt head) @@ fun () -> - let*! () = Daemon_event.head_processing hash level ~finalized:false in let* predecessor = Node_context.get_predecessor_opt node_ctxt head in match predecessor with | None -> @@ -324,7 +306,8 @@ let rec process_head (node_ctxt : _ Node_context.t) exist in the chain. *) return_unit | Some predecessor -> - let* () = process_head node_ctxt predecessor in + let* () = process_predecessor node_ctxt predecessor in + let*! () = Daemon_event.head_processing hash level in let* ctxt = previous_context node_ctxt ~predecessor head in let* () = Node_context.save_level node_ctxt head in let* inbox_hash, inbox, inbox_witness, messages = @@ -334,7 +317,7 @@ let rec process_head (node_ctxt : _ Node_context.t) when_ (Node_context.dal_supported node_ctxt) @@ fun () -> Dal_slots_tracker.process_head node_ctxt head in - let* () = process_l1_block_operations ~finalized:false node_ctxt head in + let* () = process_l1_block_operations node_ctxt head in (* Avoid storing and publishing commitments if the head is not final. *) (* Avoid triggering the pvm execution if this has been done before for this head. *) @@ -385,6 +368,9 @@ let rec process_head (node_ctxt : _ Node_context.t) let*! () = Daemon_event.new_head_processed hash level in return_unit +let rec process_head (node_ctxt : _ Node_context.t) head = + process_block node_ctxt ~process_predecessor:process_head head + (* [on_layer_1_head node_ctxt head] processes a new head from the L1. It also processes any missing blocks that were not processed. Every time a head is processed we also process head~2 as finalized (which may recursively @@ -455,7 +441,7 @@ let on_layer_1_head node_ctxt head = let daemonize (node_ctxt : _ Node_context.t) = Layer1.iter_heads node_ctxt.l1_ctxt (on_layer_1_head node_ctxt) -let degraded_refutation_mode (node_ctxt : _ Node_context.t) = +let enter_degraded_mode (node_ctxt : _ Node_context.t) = let open Lwt_result_syntax in let*! () = Daemon_event.degraded_mode () in let message = node_ctxt.Node_context.cctxt#message in @@ -463,11 +449,19 @@ let degraded_refutation_mode (node_ctxt : _ Node_context.t) = let*! () = Batcher.shutdown () in let*! () = message "Shutting down Commitment Publisher@." in let*! () = Publisher.shutdown () in - Layer1.iter_heads node_ctxt.l1_ctxt @@ fun head -> - let* () = Refutation_coordinator.process head in + return_unit + +let degraded_mode_on_block block = + let open Lwt_result_syntax in + let* () = Refutation_coordinator.process block in let*! () = Injector.inject () in return_unit +let degraded_refutation_mode node_ctxt = + let open Lwt_result_syntax in + let* () = enter_degraded_mode node_ctxt in + Layer1.iter_heads node_ctxt.l1_ctxt degraded_mode_on_block + let install_finalizer node_ctxt rpc_server = let open Lwt_syntax in Lwt_exit.register_clean_up_callback ~loc:__LOC__ @@ fun exit_status -> diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon_registration.ml b/src/proto_alpha/lib_sc_rollup_node/daemon_registration.ml new file mode 100644 index 0000000000000000000000000000000000000000..089ea4d1a09014a260c47ef36642f5cdc06872e3 --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup_node/daemon_registration.ml @@ -0,0 +1,118 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Functori, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +module Protocol_daemon : Protocol_daemon_sig.S = struct + module Node_context = struct + include Node_context + + let last_processed_block node_ctxt = + let open Lwt_result_syntax in + let+ l2_block = last_processed_head_opt node_ctxt in + let open Option_syntax in + let+ {header = {block_hash; level; _}; _} = l2_block in + (block_hash, Protocol.Alpha_context.Raw_level.to_int32 level) + end + + module RPC_server = RPC_server + + let process_block (node_ctxt : _ Node_context.t) (hash, header) = + let head = Layer1.{hash; level = header.Block_header.level} in + Daemon.process_block node_ctxt head + + let on_layer_1_head_extra (_node_ctxt : _ Node_context.t) (hash, header) = + let open Lwt_result_syntax in + let head = Layer1.{hash; level = header.Block_header.level} in + let* () = Publisher.publish_commitments () in + let* () = Publisher.cement_commitments () in + let* () = Refutation_coordinator.process head in + let* () = Batcher.batch () in + let* () = Batcher.new_head head in + let*! () = Injector.inject (* ~header *) () in + return_unit + + let enter_degraded_mode = Daemon.enter_degraded_mode + + let degraded_mode_on_block (hash, header) = + let head = Layer1.{hash; level = header.Block_header.level} in + Daemon.degraded_mode_on_block head + + let start_workers (configuration : Configuration.t) + (node_ctxt : _ Node_context.t) = + let open Lwt_result_syntax in + let signers = + Configuration.Operator_purpose_map.bindings node_ctxt.operators + |> List.fold_left + (fun acc (purpose, operator) -> + let purposes = + match Signature.Public_key_hash.Map.find operator acc with + | None -> [purpose] + | Some ps -> purpose :: ps + in + Signature.Public_key_hash.Map.add operator purposes acc) + Signature.Public_key_hash.Map.empty + |> Signature.Public_key_hash.Map.bindings + |> List.map (fun (operator, purposes) -> + let strategy = + match purposes with + | [Configuration.Add_messages] -> `Delay_block 0.5 + | _ -> `Each_block + in + (operator, strategy, purposes)) + in + let* () = Publisher.init node_ctxt in + let* () = Refutation_coordinator.init node_ctxt in + let* () = + Injector.init + node_ctxt.cctxt + (Node_context.readonly node_ctxt) + ~data_dir:node_ctxt.data_dir + ~signers + ~retention_period:configuration.injector.retention_period + ~allowed_attempts:configuration.injector.attempts + in + let* () = + match + Configuration.Operator_purpose_map.find Add_messages node_ctxt.operators + with + | None -> return_unit + | Some signer -> Batcher.init configuration.batcher ~signer node_ctxt + in + return_unit + + let stop_workers node_ctxt = + let open Lwt_result_syntax in + let message = node_ctxt.Node_context.cctxt#message in + let*! () = message "Shutting down Injector@." in + let*! () = Injector.shutdown () in + let*! () = message "Shutting down Batcher@." in + let*! () = Batcher.shutdown () in + let*! () = message "Shutting down Commitment Publisher@." in + let*! () = Publisher.shutdown () in + let*! () = message "Shutting down Refutation Coordinator@." in + let*! () = Refutation_coordinator.shutdown () in + return_unit +end + +let () = Protocol_daemons.register Protocol.hash (module Protocol_daemon) diff --git a/src/proto_alpha/lib_sc_rollup_node/node_context.ml b/src/proto_alpha/lib_sc_rollup_node/node_context.ml index 79b9f60cfe0b6a6698b8d85757d2fc6617ceae0b..7153714362d403d9e98ec173ef46f4ad8368ecc4 100644 --- a/src/proto_alpha/lib_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/lib_sc_rollup_node/node_context.ml @@ -63,6 +63,8 @@ type rw = [`Read | `Write] t type ro = [`Read] t +let block_finality_time = 2 + let () = if Sc_rollup_address.size <> Protocol.Alpha_context.Sc_rollup.Address.size then @@ -84,37 +86,41 @@ let is_accuser {mode; _} = mode = Accuser let is_loser {loser_mode; _} = loser_mode <> Loser_mode.no_failures +let origination_level {genesis_info; _} = Raw_level.to_int32 genesis_info.level + let get_fee_parameter node_ctxt purpose = Configuration.Operator_purpose_map.find purpose node_ctxt.fee_parameters |> Option.value ~default:(Configuration.default_fee_parameter ~purpose ()) +let maybe_block_id = function Some h -> `Hash (h, 0) | None -> `Head 0 + (* TODO: https://gitlab.com/tezos/tezos/-/issues/2901 The constants are retrieved from the latest tezos block. These constants can be different from the ones used at the creation at the rollup because of a protocol amendment that modifies some of them. This need to be fixed when the rollup nodes will be able to handle the migration of protocol. *) -let retrieve_constants cctxt = - Protocol.Constants_services.all cctxt (cctxt#chain, cctxt#block) +let retrieve_constants ?head cctxt = + Protocol.Constants_services.all cctxt (cctxt#chain, maybe_block_id head) -let get_last_cemented_commitment (cctxt : Protocol_client_context.full) +let get_last_cemented_commitment ?head (cctxt : Protocol_client_context.full) rollup_address = let open Lwt_result_syntax in let+ commitment, level = Plugin.RPC.Sc_rollup.last_cemented_commitment_hash_with_level cctxt - (cctxt#chain, `Head 0) + (cctxt#chain, maybe_block_id head) rollup_address in {commitment; level} -let get_last_published_commitment (cctxt : Protocol_client_context.full) +let get_last_published_commitment ?head (cctxt : Protocol_client_context.full) rollup_address operator = let open Lwt_result_syntax in let*! res = Plugin.RPC.Sc_rollup.staked_on_commitment cctxt - (cctxt#chain, `Head 0) + (cctxt#chain, maybe_block_id head) rollup_address operator in @@ -286,7 +292,7 @@ let pvm_of_kind : Protocol.Alpha_context.Sc_rollup.Kind.t -> (module Pvm.S) = | Example_arith -> (module Arith_pvm) | Wasm_2_0_0 -> (module Wasm_2_0_0_pvm) -let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file +let init (cctxt : #Client_context.full) ?head ~data_dir ?log_kernel_debug_file mode Configuration.( { @@ -301,6 +307,9 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file _; } as configuration) = let open Lwt_result_syntax in + let cctxt = + new Protocol_client_context.wrap_full (cctxt :> Client_context.full) + in let*? () = check_config configuration in let rollup_address = (* Convert to protocol rollup address *) @@ -325,11 +334,11 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file Layer1.start ~name:"sc_rollup_node" ~reconnection_delay cctxt in let publisher = Configuration.Operator_purpose_map.find Publish operators in - let* protocol_constants = retrieve_constants cctxt - and* lcc = get_last_cemented_commitment cctxt rollup_address + let* protocol_constants = retrieve_constants ?head cctxt + and* lcc = get_last_cemented_commitment ?head cctxt rollup_address and* lpc = Option.filter_map_es - (get_last_published_commitment cctxt rollup_address) + (get_last_published_commitment ?head cctxt rollup_address) publisher and* kind = RPC.Sc_rollup.kind cctxt (cctxt#chain, cctxt#block) rollup_address () @@ -384,7 +393,7 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file kind; pvm = pvm_of_kind kind; injector_retention_period = 0; - block_finality_time = 2; + block_finality_time; fee_parameters; protocol_constants; loser_mode; @@ -555,7 +564,7 @@ let head_of_block_level (hash, level) = {Layer1.hash; level} let block_level_of_head Layer1.{hash; level} = (hash, level) -let get_l2_block_predecessor node_ctxt hash = +let get_l2_predecessor node_ctxt hash = let open Lwt_result_syntax in let+ header = Store.L2_blocks.header node_ctxt.store.l2_blocks hash in Option.map @@ -565,7 +574,7 @@ let get_l2_block_predecessor node_ctxt hash = let get_predecessor_opt node_ctxt (hash, level) = let open Lwt_result_syntax in - let* pred = get_l2_block_predecessor node_ctxt hash in + let* pred = get_l2_predecessor node_ctxt hash in match pred with | Some p -> return_some p | None -> @@ -574,7 +583,7 @@ let get_predecessor_opt node_ctxt (hash, level) = let get_predecessor node_ctxt (hash, level) = let open Lwt_result_syntax in - let* pred = get_l2_block_predecessor node_ctxt hash in + let* pred = get_l2_predecessor node_ctxt hash in match pred with | Some p -> return p | None -> diff --git a/src/proto_alpha/lib_sc_rollup_node/node_context.mli b/src/proto_alpha/lib_sc_rollup_node/node_context.mli index c54137ffff356730dd440bcb4d3655a8b6d4cdc3..de10d0a707a725a703def80ee3d06312285cd685 100644 --- a/src/proto_alpha/lib_sc_rollup_node/node_context.mli +++ b/src/proto_alpha/lib_sc_rollup_node/node_context.mli @@ -89,6 +89,9 @@ type rw = [`Read | `Write] t (** Read only node context {!t}. *) type ro = [`Read] t +(** Number of blocks after which blocks are considered final. *) +val block_finality_time : int + (** [get_operator cctxt purpose] returns the public key hash for the operator who has purpose [purpose], if any. *) @@ -107,6 +110,9 @@ val is_accuser : _ t -> bool failures planned. *) val is_loser : _ t -> bool +(** Returns the origination level for the rollup used in the context. *) +val origination_level : _ t -> int32 + (** [get_fee_parameter cctxt purpose] returns the fee parameter to inject an operation for a given [purpose]. If no specific fee parameters were configured for this purpose, returns the default fee parameter for this @@ -119,12 +125,13 @@ val get_fee_parameter : protocol. *) val protocol_max_batch_size : int -(** [init cctxt ~data_dir mode configuration] initializes the rollup +(** [init cctxt ?head ~data_dir mode configuration] initializes the rollup representation. The rollup origination level and kind are fetched via an RPC call to the layer1 node that [cctxt] uses for RPC requests. *) val init : - Protocol_client_context.full -> + #Client_context.full -> + ?head:Block_hash.t -> data_dir:string -> ?log_kernel_debug_file:string -> 'a Store_sigs.mode -> @@ -219,6 +226,11 @@ val hash_of_level_opt : _ t -> int32 -> Block_hash.t option tzresult Lwt.t if it is known by the Tezos Layer 1 node. *) val level_of_hash : _ t -> Block_hash.t -> int32 tzresult Lwt.t +(** Returns the predecessor of a block hash from the information computed + locally in the L2 chain. *) +val get_l2_predecessor : + _ t -> Block_hash.t -> ((Block_hash.t * int32) option, tztrace) result Lwt.t + (** [get_predecessor_opt state head] returns the predecessor of block [head], when [head] is not the genesis block. *) val get_predecessor_opt : diff --git a/tezt/lib_tezos/protocol.ml b/tezt/lib_tezos/protocol.ml index 24a25d6abc5d309a2651c0afa0cfc52a0cf3b639..dfd9cbfe09e414b52f8c88b8876ea7d7fa8b094c 100644 --- a/tezt/lib_tezos/protocol.ml +++ b/tezt/lib_tezos/protocol.ml @@ -74,7 +74,10 @@ let accuser proto = "./octez-accuser-" ^ daemon_name proto let baker proto = "./octez-baker-" ^ daemon_name proto -let sc_rollup_node proto = "./octez-smart-rollup-node-" ^ daemon_name proto +let sc_rollup_node = function + (* TODO: remove proto prefix when ok *) + | Alpha -> "./octez-smart-rollup-node" + | proto -> "./octez-smart-rollup-node-" ^ daemon_name proto let sc_rollup_client proto = "./octez-smart-rollup-client-" ^ daemon_name proto