diff --git a/manifest/main.ml b/manifest/main.ml index d912f58d0a4f11f8facdd4526da09789c595c4a1..c534c9ba30edb4ffae0d2a4fc2a4479d21fa42df 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -4325,7 +4325,7 @@ let octez_smart_rollup_node_lib = ~synopsis:"Octez: library for Smart Rollup node" ~deps: [ - octez_base |> open_ ~m:"TzPervasives"; + octez_base |> open_ ~m:"TzPervasives" |> open_; octez_base_unix; octez_stdlib_unix |> open_; octez_crypto |> open_; @@ -4341,6 +4341,7 @@ let octez_smart_rollup_node_lib = octez_version_value |> open_; octez_layer2_store |> open_; octez_crawler |> open_; + octez_workers |> open_; octez_smart_rollup_lib |> open_; ] diff --git a/opam/octez-smart-rollup-node-lib.opam b/opam/octez-smart-rollup-node-lib.opam index 581cc5efb223b279d205b860874ae5e219589289..78f2eaa9c811a0730bf63c0ac9903bc3bd965f7c 100644 --- a/opam/octez-smart-rollup-node-lib.opam +++ b/opam/octez-smart-rollup-node-lib.opam @@ -25,6 +25,7 @@ depends: [ "tezos-version" "tezos-layer2-store" "octez-crawler" + "tezos-workers" "octez-smart-rollup" ] build: [ diff --git a/src/lib_scoru_sequencer/seq_batcher.ml b/src/lib_scoru_sequencer/seq_batcher.ml index c62e1de112826505223e146dae77f4327150d967..07f3d24a00f2382a897453f569d40c5d789263a7 100644 --- a/src/lib_scoru_sequencer/seq_batcher.ml +++ b/src/lib_scoru_sequencer/seq_batcher.ml @@ -27,7 +27,7 @@ open Protocol open Alpha_context open Octez_smart_rollup_node_alpha -open Octez_smart_rollup_node_alpha.Batcher_worker_types +open Octez_smart_rollup_node.Batcher_worker_types module Message_queue = Hash_queue.Make (L2_message.Hash) (L2_message) module Durable_state = Wasm_2_0_0_pvm.Durable_state diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher.ml b/src/lib_smart_rollup_node/batcher.ml similarity index 78% rename from src/proto_016_PtMumbai/lib_sc_rollup_node/batcher.ml rename to src/lib_smart_rollup_node/batcher.ml index e2eed2c345ebadf904558d3b09e0f812493f27e9..e7194bf3364c11a3cd9b44dd009a98cca6015c46 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher.ml +++ b/src/lib_smart_rollup_node/batcher.ml @@ -23,8 +23,6 @@ (* *) (*****************************************************************************) -open Protocol -open Alpha_context open Batcher_worker_types module Message_queue = Hash_queue.Make (L2_message.Hash) (L2_message) @@ -40,22 +38,14 @@ module Batched_messages = Hash_queue.Make (L2_message.Hash) (L2_batched_message) type status = Pending_batch | Batched of Injector.Inj_operation.hash -(* Same as {!Configuration.batcher} with max_batch_size non optional. *) -type conf = { - simulate : bool; - min_batch_elements : int; - min_batch_size : int; - max_batch_elements : int; - max_batch_size : int; -} - type state = { node_ctxt : Node_context.ro; signer : Signature.public_key_hash; - conf : conf; + conf : Configuration.batcher; messages : Message_queue.t; batched : Batched_messages.t; mutable simulation_ctxt : Simulation.t option; + mutable plugin : (module Protocol_plugin_sig.S); } let message_size s = @@ -78,6 +68,12 @@ let inject_batch state (l2_messages : L2_message.t list) = let inject_batches state = List.iter_es (inject_batch state) +let max_batch_size {conf; plugin; _} = + let module Plugin = (val plugin) in + Option.value + conf.max_batch_size + ~default:Plugin.Batcher_constants.protocol_max_batch_size + let get_batches state ~only_full = let ( current_rev_batch, current_batch_size, @@ -94,7 +90,7 @@ let get_batches state ~only_full = let new_batch_size = current_batch_size + size in let new_batch_elements = current_batch_elements + 1 in if - new_batch_size <= state.conf.max_batch_size + new_batch_size <= max_batch_size state && new_batch_elements <= state.conf.max_batch_elements then (* We can add the message to the current batch because we are still @@ -150,30 +146,27 @@ let produce_batches state ~only_full = to_remove ; return_unit -let simulate node_ctxt simulation_ctxt (messages : L2_message.t list) = +let simulate state simulation_ctxt (messages : L2_message.t list) = let open Lwt_result_syntax in + let module Plugin = (val state.plugin) in let*? ext_messages = - Environment.wrap_tzresult - @@ List.map_e - (fun m -> - let open Result_syntax in - let open Sc_rollup.Inbox_message in - let+ msg = serialize @@ External (L2_message.content m) in - unsafe_to_string msg) - messages + List.map_e + (fun m -> Plugin.Inbox.serialize_external_message (L2_message.content m)) + messages in let+ simulation_ctxt, _ticks = - Simulation.simulate_messages node_ctxt simulation_ctxt ext_messages + Simulation.simulate_messages state.node_ctxt simulation_ctxt ext_messages in simulation_ctxt let on_register state (messages : string list) = let open Lwt_result_syntax in + let module Plugin = (val state.plugin) in let max_size_msg = min - (Protocol.Constants_repr.sc_rollup_message_size_limit + (Plugin.Batcher_constants.message_size_limit + 4 (* We add 4 because [message_size] adds 4. *)) - state.conf.max_batch_size + (max_batch_size state) in let*? messages = List.mapi_e @@ -189,9 +182,7 @@ let on_register state (messages : string list) = match state.simulation_ctxt with | None -> failwith "Simulation context of batcher not initialized" | Some simulation_ctxt -> - let+ simulation_ctxt = - simulate state.node_ctxt simulation_ctxt messages - in + let+ simulation_ctxt = simulate state simulation_ctxt messages in state.simulation_ctxt <- Some simulation_ctxt in let*! () = Batcher_events.(emit queue) (List.length messages) in @@ -221,7 +212,7 @@ let on_new_head state head = (* Re-simulate one by one *) Message_queue.fold_es (fun msg_hash msg (simulation_ctxt, failing) -> - let*! result = simulate state.node_ctxt simulation_ctxt [msg] in + let*! result = simulate state simulation_ctxt [msg] in match result with | Ok simulation_ctxt -> return (simulation_ctxt, failing) | Error _ -> return (simulation_ctxt, msg_hash :: failing)) @@ -232,65 +223,23 @@ let on_new_head state head = (* Forget failing messages *) List.iter (Message_queue.remove state.messages) failing -(** Maximum size of an L2 batch in bytes that can fit in an operation of the - protocol. *) -let protocol_max_batch_size = - let open Protocol in - let open Alpha_context in - let empty_message_op : _ Operation.t = - let open Operation in - { - shell = {branch = Block_hash.zero}; - protocol_data = - { - signature = Some Signature.zero; - contents = - Single - (Manager_operation - { - source = Signature.Public_key_hash.zero; - fee = Tez.of_mutez_exn Int64.max_int; - counter = Manager_counter.Internal_for_tests.of_int max_int; - gas_limit = - Gas.Arith.integral_of_int_exn ((max_int - 1) / 1000); - storage_limit = Z.of_int max_int; - operation = Sc_rollup_add_messages {messages = [""]}; - }); - }; - } - in - Protocol.Constants_repr.max_operation_data_length - - Data_encoding.Binary.length - Operation.encoding - (Operation.pack empty_message_op) - -let init_batcher_state node_ctxt ~signer (conf : Configuration.batcher) = - let open Lwt_syntax in - let conf = - { - simulate = conf.simulate; - min_batch_elements = conf.min_batch_elements; - min_batch_size = conf.min_batch_size; - max_batch_elements = conf.max_batch_elements; - max_batch_size = - Option.value conf.max_batch_size ~default:protocol_max_batch_size; - } - in - return - { - node_ctxt; - signer; - conf; - messages = Message_queue.create 100_000 (* ~ 400MB *); - batched = Batched_messages.create 100_000 (* ~ 400MB *); - simulation_ctxt = None; - } +let init_batcher_state plugin node_ctxt ~signer (conf : Configuration.batcher) = + { + node_ctxt; + signer; + conf; + messages = Message_queue.create 100_000 (* ~ 400MB *); + batched = Batched_messages.create 100_000 (* ~ 400MB *); + simulation_ctxt = None; + plugin; + } module Types = struct type nonrec state = state type parameters = { node_ctxt : Node_context.ro; + plugin : (module Protocol_plugin_sig.S); signer : Signature.public_key_hash; conf : Configuration.batcher; } @@ -329,9 +278,9 @@ module Handlers = struct type launch_error = error trace - let on_launch _w () Types.{node_ctxt; signer; conf} = + let on_launch _w () Types.{node_ctxt; plugin; signer; conf} = let open Lwt_result_syntax in - let*! state = init_batcher_state node_ctxt ~signer conf in + let state = init_batcher_state plugin node_ctxt ~signer conf in return state let on_error (type a b) _w st (r : (a, b) Request.t) (errs : b) : @@ -362,24 +311,25 @@ let table = Worker.create_table Queue let worker_promise, worker_waker = Lwt.task () -let check_batcher_config Configuration.{max_batch_size; _} = +let check_batcher_config (module Plugin : Protocol_plugin_sig.S) + Configuration.{max_batch_size; _} = match max_batch_size with - | Some m when m > protocol_max_batch_size -> + | Some m when m > Plugin.Batcher_constants.protocol_max_batch_size -> error_with "batcher.max_batch_size must be smaller than %d" - protocol_max_batch_size + Plugin.Batcher_constants.protocol_max_batch_size | _ -> Ok () -let start conf ~signer node_ctxt = +let start plugin conf ~signer node_ctxt = let open Lwt_result_syntax in - let*? () = check_batcher_config conf in + let*? () = check_batcher_config plugin conf in let node_ctxt = Node_context.readonly node_ctxt in let+ worker = - Worker.launch table () {node_ctxt; signer; conf} (module Handlers) + Worker.launch table () {node_ctxt; plugin; signer; conf} (module Handlers) in Lwt.wakeup worker_waker worker -let init conf ~signer node_ctxt = +let init plugin conf ~signer node_ctxt = let open Lwt_result_syntax in match Lwt.state worker_promise with | Lwt.Return _ -> @@ -387,17 +337,18 @@ let init conf ~signer node_ctxt = return_unit | Lwt.Fail exn -> (* Worker crashed, not recoverable. *) - fail [Sc_rollup_node_errors.No_batcher; Exn exn] + fail [Rollup_node_errors.No_batcher; Exn exn] | Lwt.Sleep -> (* Never started, start it. *) - start conf ~signer node_ctxt + start plugin conf ~signer node_ctxt (* This is a batcher worker for a single scoru *) let worker = lazy (match Lwt.state worker_promise with - | Lwt.Return worker -> ok worker - | Lwt.Fail _ | Lwt.Sleep -> error Sc_rollup_node_errors.No_batcher) + | Lwt.Return worker -> Ok worker + | Lwt.Fail _ | Lwt.Sleep -> + Error (TzTrace.make Rollup_node_errors.No_batcher)) let active () = match Lwt.state worker_promise with diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher.mli b/src/lib_smart_rollup_node/batcher.mli similarity index 78% rename from src/proto_017_PtNairob/lib_sc_rollup_node/batcher.mli rename to src/lib_smart_rollup_node/batcher.mli index f38610bade8e27745ce4bdab2d7f0bfdb09ce572..83eef392c313fcff7d99521dba6113ac0d379198 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher.mli +++ b/src/lib_smart_rollup_node/batcher.mli @@ -23,8 +23,6 @@ (* *) (*****************************************************************************) -include Daemon_components.Batcher_sig - (** The type for the status of messages in the batcher. *) type status = | Pending_batch (** The message is in the queue of the batcher. *) @@ -32,6 +30,27 @@ type status = (** The message has already been batched and sent to the injector in an L1 operation whose hash is given. *) +(** [init plugin config ~signer node_ctxt] initializes and starts the batcher + for [signer]. If [config.simulation] is [true] (the default), messages added + to the batcher are simulated in an incremental simulation context. [plugin] + is the protocol plugin with which the batcher is started, but it will + automatically change plugins on protocol migrations. *) +val init : + (module Protocol_plugin_sig.S) -> + Configuration.batcher -> + signer:Signature.public_key_hash -> + _ Node_context.t -> + unit tzresult Lwt.t + +(** Create L2 batches of operations from the queue and pack each batch in an L1 + operation. The L1 operations (i.e. L2 batches) are queued in the injector for + injection on the Tezos node. +*) +val new_head : Layer1.head -> unit tzresult Lwt.t + +(** Shutdown the batcher, waiting for the ongoing request to be processed. *) +val shutdown : unit -> unit Lwt.t + (** Return [true] if the batcher was started for this node. *) val active : unit -> bool diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/batcher_events.ml b/src/lib_smart_rollup_node/batcher_events.ml similarity index 98% rename from src/proto_018_Proxford/lib_sc_rollup_node/batcher_events.ml rename to src/lib_smart_rollup_node/batcher_events.ml index abe012a2dd0d266f6970b8f12980736f4f22a7c3..63b52a14caad23b5e6d85fe7064baa8159b357b2 100644 --- a/src/proto_018_Proxford/lib_sc_rollup_node/batcher_events.ml +++ b/src/lib_smart_rollup_node/batcher_events.ml @@ -29,7 +29,7 @@ end) = struct include Internal_event.Simple - let section = [Protocol.name; "sc_rollup_node"; WORKER.worker_name] + let section = ["sc_rollup_node"; WORKER.worker_name] let queue = declare_1 diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_worker_types.ml b/src/lib_smart_rollup_node/batcher_worker_types.ml similarity index 100% rename from src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_worker_types.ml rename to src/lib_smart_rollup_node/batcher_worker_types.ml diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_worker_types.mli b/src/lib_smart_rollup_node/batcher_worker_types.mli similarity index 100% rename from src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_worker_types.mli rename to src/lib_smart_rollup_node/batcher_worker_types.mli diff --git a/src/lib_smart_rollup_node/dune b/src/lib_smart_rollup_node/dune index f545540ba13f891f475f4fa802858c974f490cdb..aa095183fd57184f500b1bd2a0a1d467e14df930 100644 --- a/src/lib_smart_rollup_node/dune +++ b/src/lib_smart_rollup_node/dune @@ -22,10 +22,12 @@ tezos-version.value tezos_layer2_store octez-crawler + tezos-workers octez-smart-rollup) (flags (:standard) -open Tezos_base.TzPervasives + -open Tezos_base -open Tezos_stdlib_unix -open Tezos_crypto -open Tezos_client_base @@ -37,4 +39,5 @@ -open Tezos_version_value -open Tezos_layer2_store -open Octez_crawler + -open Tezos_workers -open Octez_smart_rollup)) diff --git a/src/lib_smart_rollup_node/protocol_plugin_sig.ml b/src/lib_smart_rollup_node/protocol_plugin_sig.ml index 2c9f52ef69312a16440423ef171673b48e5b55a4..fd472fa37024074647dc511a152d24b8c124c5f7 100644 --- a/src/lib_smart_rollup_node/protocol_plugin_sig.ml +++ b/src/lib_smart_rollup_node/protocol_plugin_sig.ml @@ -75,6 +75,11 @@ module type INBOX = sig Octez_smart_rollup.Inbox.t -> unit tzresult Lwt.t + (** Serialize an external messages to the protocol representation. NOTE: so + far, in all available protocols, this adds a tag ['\001'] at the + beginning. *) + val serialize_external_message : string -> string tzresult + (**/**) module Internal_for_tests : sig @@ -183,6 +188,17 @@ module type REFUTATION_COORDINATOR = sig val shutdown : unit -> unit Lwt.t end +(** Protocol specific constants for the batcher. *) +module type BATCHER_CONSTANTS = sig + (** Maximum size of an L2 message allowed by the prototcol, which is + {!val:Protocol.Constants_repr.sc_rollup_message_size_limit}. *) + val message_size_limit : int + + (** Maximum size in bytes of an batch of L2 messages that can fit in an + operation on L1. It is protocol dependent. *) + val protocol_max_batch_size : int +end + (** Protocol specific batcher. NOTE: The batcher has to be stopped and the new one restarted on protocol change. *) module type BATCHER = sig @@ -292,7 +308,7 @@ module type S = sig module Refutation_coordinator : REFUTATION_COORDINATOR - module Batcher : BATCHER + module Batcher_constants : BATCHER_CONSTANTS module Layer1_helpers : LAYER1_HELPERS diff --git a/src/lib_smart_rollup_node/rollup_node_daemon.ml b/src/lib_smart_rollup_node/rollup_node_daemon.ml index c2605f6bdb4c732e018dd9fa53b8e484670c8c9c..019e5942fdc751ae145edfb63dd2d0fedf391d34 100644 --- a/src/lib_smart_rollup_node/rollup_node_daemon.ml +++ b/src/lib_smart_rollup_node/rollup_node_daemon.ml @@ -58,8 +58,9 @@ let previous_context (node_ctxt : _ Node_context.t) else Node_context.checkout_context node_ctxt predecessor.Layer1.hash let start_workers ?(degraded = false) (configuration : Configuration.t) - (module Plugin : Protocol_plugin_sig.S) (node_ctxt : _ Node_context.t) = + (plugin : (module Protocol_plugin_sig.S)) (node_ctxt : _ Node_context.t) = let open Lwt_result_syntax in + let module Plugin = (val plugin) in let* () = unless degraded @@ fun () -> let* () = Plugin.Publisher.init node_ctxt in @@ -67,7 +68,7 @@ let start_workers ?(degraded = false) (configuration : Configuration.t) Configuration.Operator_purpose_map.find Add_messages node_ctxt.operators with | None -> return_unit - | Some signer -> Plugin.Batcher.init configuration.batcher ~signer node_ctxt + | Some signer -> Batcher.init plugin configuration.batcher ~signer node_ctxt in let* () = Plugin.Refutation_coordinator.init node_ctxt in return_unit @@ -272,7 +273,7 @@ let on_layer_1_head ({node_ctxt; _} as state) (head : Layer1.header) = let* () = Plugin.Publisher.cement_commitments () in let*! () = Daemon_event.new_heads_processed reorg.new_chain in let* () = Plugin.Refutation_coordinator.process stripped_head in - let* () = Plugin.Batcher.new_head stripped_head in + let* () = Batcher.new_head stripped_head in let*! () = Injector.inject ~header:head.header () in return_unit @@ -285,7 +286,7 @@ let degraded_refutation_mode state = let message = state.node_ctxt.Node_context.cctxt#message in let module Plugin = (val state.plugin) in let*! () = message "Shutting down Batcher@." in - let*! () = Plugin.Batcher.shutdown () in + let*! () = Batcher.shutdown () in let*! () = message "Shutting down Commitment Publisher@." in let*! () = Plugin.Publisher.shutdown () in Layer1.iter_heads state.node_ctxt.l1_ctxt @@ fun head -> @@ -309,7 +310,7 @@ let install_finalizer state = let* () = message "Shutting down Injector@." in let* () = Injector.shutdown () in let* () = message "Shutting down Batcher@." in - let* () = Plugin.Batcher.shutdown () in + let* () = Batcher.shutdown () in let* () = message "Shutting down Commitment Publisher@." in let* () = Plugin.Publisher.shutdown () in let* () = message "Shutting down Refutation Coordinator@." in diff --git a/src/lib_smart_rollup_node/rpc_directory.ml b/src/lib_smart_rollup_node/rpc_directory.ml index 296c3a4ab7db59e7538581bb17a1ce1c108080ab..95663b4dcdc756c1d97d5f20e703bed97de9fc55 100644 --- a/src/lib_smart_rollup_node/rpc_directory.ml +++ b/src/lib_smart_rollup_node/rpc_directory.ml @@ -45,13 +45,6 @@ let get_proto_plugin_of_level node_ctxt level = let*? plugin = Protocol_plugins.proto_plugin_for_protocol proto.protocol in return plugin -let get_last_proto_plugin node_ctxt = - let open Lwt_result_syntax in - let* head = Node_context.last_processed_head_opt node_ctxt in - match head with - | None -> failwith "No processed head, could not determine last protocol" - | Some head -> get_proto_plugin_of_level node_ctxt head.header.level - module Global_directory = Make_directory (struct include Rollup_node_services.Global @@ -126,17 +119,13 @@ let () = let () = Local_directory.register0 Rollup_node_services.Local.injection - @@ fun node_ctxt () messages -> - let open Lwt_result_syntax in - let* (module Plugin) = get_last_proto_plugin node_ctxt in - Plugin.Batcher.register_messages messages + @@ fun _node_ctxt () messages -> Batcher.register_messages messages let () = Local_directory.register0 Rollup_node_services.Local.batcher_queue - @@ fun node_ctxt () () -> + @@ fun _node_ctxt () () -> let open Lwt_result_syntax in - let* (module Plugin) = get_last_proto_plugin node_ctxt in - let*? queue = Plugin.Batcher.get_queue () in + let*? queue = Batcher.get_queue () in return queue (** [commitment_level_of_inbox_level node_ctxt inbox_level] returns the level @@ -175,8 +164,7 @@ let () = Local_directory.register1 Rollup_node_services.Local.batcher_message @@ fun node_ctxt hash () () -> let open Lwt_result_syntax in - let* (module Plugin) = get_last_proto_plugin node_ctxt in - let*? batch_status = Plugin.Batcher.message_status hash in + let*? batch_status = Batcher.message_status hash in let* status = match batch_status with | None -> return (None, Rollup_node_services.Unknown) diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher.mli deleted file mode 100644 index f38610bade8e27745ce4bdab2d7f0bfdb09ce572..0000000000000000000000000000000000000000 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher.mli +++ /dev/null @@ -1,55 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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. *) -(* *) -(*****************************************************************************) - -include Daemon_components.Batcher_sig - -(** The type for the status of messages in the batcher. *) -type status = - | Pending_batch (** The message is in the queue of the batcher. *) - | Batched of Injector.Inj_operation.hash - (** The message has already been batched and sent to the injector in an L1 - operation whose hash is given. *) - -(** Return [true] if the batcher was started for this node. *) -val active : unit -> bool - -(** Retrieve an L2 message from the queue. *) -val find_message : L2_message.hash -> L2_message.t option tzresult - -(** List all queued messages in the order they appear in the queue, i.e. the - message that were added first to the queue are at the end of list. *) -val get_queue : unit -> (L2_message.hash * L2_message.t) list tzresult - -(** [register_messages messages] registers new L2 [messages] in the queue of the - batcher for future injection on L1. If the batcher was initialized with - [simualte = true], the messages are evaluated the batcher's incremental - simulation context. In this case, when the application fails, the messages - are not queued. *) -val register_messages : string list -> L2_message.hash list tzresult Lwt.t - -(** The status of a message in the batcher. Returns [None] if the message is not - known by the batcher (the batcher only keeps the batched status of the last - 500000 messages). *) -val message_status : L2_message.hash -> (status * string) option tzresult diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_worker_types.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_constants.ml similarity index 58% rename from src/proto_017_PtNairob/lib_sc_rollup_node/batcher_worker_types.ml rename to src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_constants.ml index f7ab4535c26b164841fd61fb10f6d4dd0134ad54..edfa3ea32f006f8995fb2ca4999e28d87e98df71 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_worker_types.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_constants.ml @@ -1,7 +1,7 @@ (*****************************************************************************) (* *) (* 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"),*) @@ -23,47 +23,34 @@ (* *) (*****************************************************************************) -module Request = struct - type ('a, 'b) t = - | Register : string list -> (L2_message.hash list, error trace) t - | New_head : Layer1.head -> (unit, error trace) t +let message_size_limit = Protocol.Constants_repr.sc_rollup_message_size_limit - type view = View : _ t -> view - - let view req = View req - - let encoding = - let open Data_encoding in - union - [ - case - (Tag 0) - ~title:"Register" - (obj2 - (req "request" (constant "register")) - (req "messages" (list L2_message.content_encoding))) - (function - | View (Register messages) -> Some ((), messages) | _ -> None) - (fun ((), messages) -> View (Register messages)); - case - (Tag 1) - ~title:"New_head" - (obj2 - (req "request" (constant "new_head")) - (req "block" Layer1.head_encoding)) - (function View (New_head b) -> Some ((), b) | _ -> None) - (fun ((), b) -> View (New_head b)); - ] - - let pp ppf (View r) = - match r with - | Register messages -> - Format.fprintf ppf "register %d new L2 message" (List.length messages) - | New_head {Layer1.hash; level} -> - Format.fprintf - ppf - "switching to new L1 head %a at level %ld" - Block_hash.pp - hash - level -end +let protocol_max_batch_size = + let open Protocol in + let open Alpha_context in + let empty_message_op : _ Operation.t = + let open Operation in + { + shell = {branch = Block_hash.zero}; + protocol_data = + { + signature = Some Signature.zero; + contents = + Single + (Manager_operation + { + source = Signature.Public_key_hash.zero; + fee = Tez.of_mutez_exn Int64.max_int; + counter = Manager_counter.Internal_for_tests.of_int max_int; + gas_limit = + Gas.Arith.integral_of_int_exn ((max_int - 1) / 1000); + storage_limit = Z.of_int max_int; + operation = Sc_rollup_add_messages {messages = [""]}; + }); + }; + } + in + Protocol.Constants_repr.max_operation_data_length + - Data_encoding.Binary.length + Operation.encoding + (Operation.pack empty_message_op) diff --git a/src/proto_alpha/lib_sc_rollup_node/batcher_worker_types.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_constants.mli similarity index 73% rename from src/proto_alpha/lib_sc_rollup_node/batcher_worker_types.mli rename to src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_constants.mli index fd6ba038ada187a063446a07044418a5b878cb0a..dda41ddc6be1ad185be0997f1f289daae059f0c2 100644 --- a/src/proto_alpha/lib_sc_rollup_node/batcher_worker_types.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_constants.mli @@ -1,7 +1,7 @@ (*****************************************************************************) (* *) (* 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"),*) @@ -23,21 +23,10 @@ (* *) (*****************************************************************************) -(** This module contains the parameters for the worker (see {!Worker}) used by - the batcher. *) +(** Maximum size of an L2 message allowed by the prototcol. Is + {!val:Protocol.Constants_repr.sc_rollup_message_size_limit}. *) +val message_size_limit : int -module Request : sig - (** Type of requests accepted by the batcher worker. *) - type ('a, 'b) t = - | Register : string list -> (L2_message.hash list, error trace) t - (** Request to register new L2 messages in the queue. *) - | New_head : Layer1.head -> (unit, error trace) t - (** Request to handle a new L1 head. *) - - type view = View : _ t -> view - - include - Worker_intf.REQUEST - with type ('a, 'request_error) t := ('a, 'request_error) t - and type view := view -end +(** Maximum size in bytes of an batch of L2 messages that can fit in an + operation on L1. It is protocol dependent. *) +val protocol_max_batch_size : int diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_events.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_events.ml deleted file mode 100644 index abe012a2dd0d266f6970b8f12980736f4f22a7c3..0000000000000000000000000000000000000000 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/batcher_events.ml +++ /dev/null @@ -1,91 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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 Declare (WORKER : sig - val worker_name : string -end) = -struct - include Internal_event.Simple - - let section = [Protocol.name; "sc_rollup_node"; WORKER.worker_name] - - let queue = - declare_1 - ~section - ~name:"queue" - ~msg:"adding {nb_messages} to queue" - ~level:Notice - ("nb_messages", Data_encoding.int31) - - let batched = - declare_2 - ~section - ~name:"batched" - ~msg:"batched {nb_messages} messages into {nb_batches} batches" - ~level:Notice - ("nb_batches", Data_encoding.int31) - ("nb_messages", Data_encoding.int31) - - module Worker = struct - open Batcher_worker_types - - let section = section @ ["worker"] - - let request_failed = - declare_3 - ~section - ~name:"request_failed" - ~msg:"request {view} failed ({worker_status}): {errors}" - ~level:Warning - ("view", Request.encoding) - ~pp1:Request.pp - ("worker_status", Worker_types.request_status_encoding) - ~pp2:Worker_types.pp_status - ("errors", Error_monad.trace_encoding) - ~pp3:Error_monad.pp_print_trace - - let request_completed_notice = - declare_2 - ~section - ~name:"request_completed_notice" - ~msg:"{view} {worker_status}" - ~level:Notice - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - - let request_completed_debug = - declare_2 - ~section - ~name:"request_completed_debug" - ~msg:"{view} {worker_status}" - ~level:Debug - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - end -end diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml index 2238a82fa858c3ea6e7e4ab8a5b00991bcb81568..7fe0963b96c59246b08e959271d5efc3ca61e3a1 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml @@ -25,288 +25,7 @@ (* *) (*****************************************************************************) -open Protocol -open Alpha_context -open Apply_results - -(** Returns [Some c] if [their_commitment] is refutable where [c] is our - commitment for the same inbox level. *) -let is_refutable_commitment node_ctxt - (their_commitment : Octez_smart_rollup.Commitment.t) their_commitment_hash = - let open Lwt_result_syntax in - let* l2_block = - Node_context.get_l2_block_by_level node_ctxt their_commitment.inbox_level - in - let* our_commitment_and_hash = - Option.filter_map_es - (fun hash -> - let+ commitment = Node_context.find_commitment node_ctxt hash in - Option.map (fun c -> (c, hash)) commitment) - l2_block.header.commitment_hash - in - match our_commitment_and_hash with - | Some (our_commitment, our_commitment_hash) - when Octez_smart_rollup.Commitment.Hash.( - their_commitment_hash <> our_commitment_hash - && their_commitment.predecessor = our_commitment.predecessor) -> - return our_commitment_and_hash - | _ -> return_none - -(** Publish a commitment when an accuser node sees a refutable commitment. *) -let accuser_publish_commitment_when_refutable node_ctxt ~other rollup - their_commitment their_commitment_hash = - let open Lwt_result_syntax in - when_ (Node_context.is_accuser node_ctxt) @@ fun () -> - (* We are seeing a commitment from someone else. We check if we agree - with it, otherwise the accuser publishes our commitment in order to - play the refutation game. *) - let* refutable = - is_refutable_commitment node_ctxt their_commitment their_commitment_hash - in - match refutable with - | None -> return_unit - | Some (our_commitment, our_commitment_hash) -> - let*! () = - Refutation_game_event.potential_conflict_detected - ~our_commitment_hash - ~their_commitment_hash - ~level:their_commitment.inbox_level - ~other - in - assert (Octez_smart_rollup.Address.(node_ctxt.rollup_address = rollup)) ; - Publisher.publish_single_commitment node_ctxt our_commitment - -(** Process an L1 SCORU operation (for the node's rollup) which is included for - the first time. {b Note}: this function does not process inboxes for the - rollup, which is done instead by {!Inbox.process_head}. *) -let process_included_l1_operation (type kind) (node_ctxt : Node_context.rw) - (head : Layer1.header) ~source (operation : kind manager_operation) - (result : kind successful_manager_operation_result) = - let open Lwt_result_syntax in - match (operation, result) with - | ( Sc_rollup_publish {commitment; _}, - Sc_rollup_publish_result {published_at_level; _} ) - when Node_context.is_operator node_ctxt source -> - (* Published commitment --------------------------------------------- *) - let save_lpc = - match Reference.get node_ctxt.lpc with - | None -> true - | Some lpc -> - Raw_level.to_int32 commitment.inbox_level >= lpc.inbox_level - in - let commitment = Sc_rollup_proto_types.Commitment.to_octez commitment in - if save_lpc then Reference.set node_ctxt.lpc (Some commitment) ; - let commitment_hash = Octez_smart_rollup.Commitment.hash commitment in - let* () = - Node_context.set_commitment_published_at_level - node_ctxt - commitment_hash - { - first_published_at_level = Raw_level.to_int32 published_at_level; - published_at_level = Some head.Layer1.level; - } - in - let*! () = - Commitment_event.last_published_commitment_updated - commitment_hash - head.Layer1.level - in - return_unit - | ( Sc_rollup_publish {commitment = their_commitment; rollup}, - Sc_rollup_publish_result - {published_at_level; staked_hash = their_commitment_hash; _} ) -> - (* Commitment published by someone else *) - (* We first register the publication information *) - let their_commitment_hash = - Sc_rollup_proto_types.Commitment_hash.to_octez their_commitment_hash - in - let* known_commitment = - Node_context.commitment_exists node_ctxt their_commitment_hash - in - let* () = - if not known_commitment then return_unit - else - let* republication = - Node_context.commitment_was_published - node_ctxt - ~source:Anyone - their_commitment_hash - in - if republication then return_unit - else - let* () = - Node_context.set_commitment_published_at_level - node_ctxt - their_commitment_hash - { - first_published_at_level = - Raw_level.to_int32 published_at_level; - published_at_level = None; - } - in - return_unit - in - (* An accuser node will publish its commitment if the other one is - refutable. *) - let rollup = Sc_rollup_proto_types.Address.to_octez rollup in - let their_commitment = - Sc_rollup_proto_types.Commitment.to_octez their_commitment - in - accuser_publish_commitment_when_refutable - node_ctxt - ~other:source - rollup - their_commitment - their_commitment_hash - | Sc_rollup_cement {commitment; _}, Sc_rollup_cement_result {inbox_level; _} - -> - (* Cemented commitment ---------------------------------------------- *) - let inbox_level = Raw_level.to_int32 inbox_level in - let* inbox_block = - Node_context.get_l2_block_by_level node_ctxt inbox_level - in - let commitment_hash = - Sc_rollup_proto_types.Commitment_hash.to_octez commitment - in - let*? () = - (* We stop the node if we disagree with a cemented commitment *) - let our_commitment_hash = inbox_block.header.commitment_hash in - error_unless - (Option.equal - Octez_smart_rollup.Commitment.Hash.( = ) - our_commitment_hash - (Some commitment_hash)) - (Sc_rollup_node_errors.Disagree_with_cemented - {inbox_level; ours = our_commitment_hash; on_l1 = commitment_hash}) - in - let lcc = Reference.get node_ctxt.lcc in - let*! () = - if inbox_level > lcc.level then ( - Reference.set - node_ctxt.lcc - {commitment = commitment_hash; level = inbox_level} ; - Commitment_event.last_cemented_commitment_updated - commitment_hash - inbox_level) - else Lwt.return_unit - in - return_unit - | ( Sc_rollup_refute _, - Sc_rollup_refute_result {game_status = Ended end_status; _} ) - | ( Sc_rollup_timeout _, - Sc_rollup_timeout_result {game_status = Ended end_status; _} ) -> ( - match end_status with - | Loser {loser; reason} when Node_context.is_operator node_ctxt loser -> - let result = - match reason with - | Conflict_resolved -> Sc_rollup_node_errors.Conflict_resolved - | Timeout -> Timeout - in - tzfail (Sc_rollup_node_errors.Lost_game result) - | Loser _ -> - (* Other player lost *) - return_unit - | Draw -> - let stakers = - match operation with - | Sc_rollup_refute {opponent; _} -> [source; opponent] - | Sc_rollup_timeout {stakers = {alice; bob}; _} -> [alice; bob] - | _ -> assert false - in - fail_when - (List.exists (Node_context.is_operator node_ctxt) stakers) - (Sc_rollup_node_errors.Lost_game Draw)) - | Dal_publish_slot_header slot_header, Dal_publish_slot_header_result _ - when Node_context.dal_supported node_ctxt -> - let* () = - Node_context.save_slot_header - node_ctxt - ~published_in_block_hash:head.Layer1.hash - (Sc_rollup_proto_types.Dal.Slot_header.to_octez slot_header.header) - in - return_unit - | _, _ -> - (* Other manager operations *) - return_unit - -let process_l1_operation (type kind) node_ctxt (head : Layer1.header) ~source - (operation : kind manager_operation) - (result : kind Apply_results.manager_operation_result) = - let open Lwt_result_syntax in - let is_for_my_rollup : type kind. kind manager_operation -> bool = function - | Sc_rollup_add_messages _ -> true - | Sc_rollup_cement {rollup; _} - | Sc_rollup_publish {rollup; _} - | Sc_rollup_refute {rollup; _} - | Sc_rollup_timeout {rollup; _} - | Sc_rollup_execute_outbox_message {rollup; _} - | Sc_rollup_recover_bond {sc_rollup = rollup; staker = _} -> - Octez_smart_rollup.Address.( - Sc_rollup_proto_types.Address.to_octez rollup - = node_ctxt.Node_context.rollup_address) - | Dal_publish_slot_header _ -> true - | Reveal _ | Transaction _ | Origination _ | Delegation _ - | Update_consensus_key _ | Register_global_constant _ | Set_deposits_limit _ - | Increase_paid_storage _ | Tx_rollup_origination | Tx_rollup_submit_batch _ - | Tx_rollup_commit _ | Tx_rollup_return_bond _ - | Tx_rollup_finalize_commitment _ | Tx_rollup_remove_commitment _ - | Tx_rollup_rejection _ | Tx_rollup_dispatch_tickets _ | Transfer_ticket _ - | Sc_rollup_originate _ | Zk_rollup_origination _ | Zk_rollup_publish _ - | Zk_rollup_update _ -> - false - in - if not (is_for_my_rollup operation) then return_unit - else - (* Only look at operations that are for the node's rollup *) - let*! () = - match Sc_rollup_injector.injector_operation_of_manager operation with - | None -> Lwt.return_unit - | Some op -> - let status, errors = - match result with - | Applied _ -> (`Applied, None) - | Backtracked (_, e) -> - (`Backtracked, Option.map Environment.wrap_tztrace e) - | Failed (_, e) -> (`Failed, Some (Environment.wrap_tztrace e)) - | Skipped _ -> (`Skipped, None) - in - Daemon_event.included_operation ?errors status op - in - match result with - | Applied success_result -> - process_included_l1_operation - node_ctxt - head - ~source - operation - success_result - | _ -> - (* No action for non successful operations *) - return_unit - -let process_l1_block_operations node_ctxt (head : Layer1.header) = - let open Lwt_result_syntax in - let* block = - Layer1_helpers.fetch_tezos_block node_ctxt.Node_context.l1_ctxt head.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 node_ctxt head ~source operation result - in - let apply_internal (type kind) accu ~source:_ - (_operation : kind Apply_internal_results.internal_operation) - (_result : kind Apply_internal_results.internal_operation_result) = - accu - in - let* () = - Layer1_services.process_manager_operations - return_unit - block.operations - {apply; apply_internal} - in - return_unit +open Daemon_helpers let before_origination (node_ctxt : _ Node_context.t) (header : Layer1.header) = let origination_level = node_ctxt.genesis_info.level in @@ -561,37 +280,11 @@ let install_finalizer (daemon_components : (module Daemon_components.S)) let* () = Event.shutdown_node exit_status in Tezos_base_unix.Internal_event_unix.close () -let check_initial_state_hash {Node_context.cctxt; rollup_address; kind; _} = - let open Lwt_result_syntax in - let module PVM = (val Pvm.of_kind kind) in - let* l1_reference_initial_state_hash = - RPC.Sc_rollup.initial_pvm_state_hash - (new Protocol_client_context.wrap_full cctxt) - (cctxt#chain, cctxt#block) - (Sc_rollup_proto_types.Address.of_octez rollup_address) - in - let*! s = PVM.initial_state ~empty:(PVM.State.empty ()) in - let*! l2_initial_state_hash = PVM.state_hash s in - let l1_reference_initial_state_hash = - Sc_rollup_proto_types.State_hash.to_octez l1_reference_initial_state_hash - in - let l2_initial_state_hash = - Sc_rollup_proto_types.State_hash.to_octez l2_initial_state_hash - in - fail_unless - Octez_smart_rollup.State_hash.( - l1_reference_initial_state_hash = l2_initial_state_hash) - (Sc_rollup_node_errors.Wrong_initial_pvm_state - { - initial_state_hash = l2_initial_state_hash; - expected_state_hash = l1_reference_initial_state_hash; - }) - let run node_ctxt configuration (daemon_components : (module Daemon_components.S)) = let open Lwt_result_syntax in let (module Components) = daemon_components in - let* () = check_initial_state_hash node_ctxt in + let* () = check_pvm_initial_state_hash node_ctxt in let* rpc_server = RPC_server.start node_ctxt configuration in let (_ : Lwt_exit.clean_up_callback_id) = install_finalizer daemon_components node_ctxt rpc_server @@ -770,7 +463,12 @@ module Internal_for_tests = struct end module Rollup_node_daemon_components : Daemon_components.S = struct - module Batcher = Batcher + module Batcher = struct + include Batcher + + let init c = init (module Rollup_node_plugin.Plugin) c + end + module RPC_server = RPC_server end diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon_helpers.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon_helpers.ml new file mode 100644 index 0000000000000000000000000000000000000000..0ad1b6ba210e2f27bfea254457859a22f7d7459b --- /dev/null +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon_helpers.ml @@ -0,0 +1,335 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs, *) +(* Copyright (c) 2023 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context +open Apply_results + +let check_pvm_initial_state_hash {Node_context.cctxt; rollup_address; kind; _} = + let open Lwt_result_syntax in + let module PVM = (val Pvm.of_kind kind) in + let* l1_reference_initial_state_hash = + RPC.Sc_rollup.initial_pvm_state_hash + (new Protocol_client_context.wrap_full cctxt) + (cctxt#chain, cctxt#block) + (Sc_rollup_proto_types.Address.of_octez rollup_address) + in + let*! s = PVM.initial_state ~empty:(PVM.State.empty ()) in + let*! l2_initial_state_hash = PVM.state_hash s in + let l1_reference_initial_state_hash = + Sc_rollup_proto_types.State_hash.to_octez l1_reference_initial_state_hash + in + let l2_initial_state_hash = + Sc_rollup_proto_types.State_hash.to_octez l2_initial_state_hash + in + fail_unless + Octez_smart_rollup.State_hash.( + l1_reference_initial_state_hash = l2_initial_state_hash) + (Sc_rollup_node_errors.Wrong_initial_pvm_state + { + initial_state_hash = l2_initial_state_hash; + expected_state_hash = l1_reference_initial_state_hash; + }) + +(** Returns [Some c] if [their_commitment] is refutable where [c] is our + commitment for the same inbox level. *) +let is_refutable_commitment node_ctxt + (their_commitment : Octez_smart_rollup.Commitment.t) their_commitment_hash = + let open Lwt_result_syntax in + let* l2_block = + Node_context.get_l2_block_by_level node_ctxt their_commitment.inbox_level + in + let* our_commitment_and_hash = + Option.filter_map_es + (fun hash -> + let+ commitment = Node_context.find_commitment node_ctxt hash in + Option.map (fun c -> (c, hash)) commitment) + l2_block.header.commitment_hash + in + match our_commitment_and_hash with + | Some (our_commitment, our_commitment_hash) + when Octez_smart_rollup.Commitment.Hash.( + their_commitment_hash <> our_commitment_hash + && their_commitment.predecessor = our_commitment.predecessor) -> + return our_commitment_and_hash + | _ -> return_none + +(** Publish a commitment when an accuser node sees a refutable commitment. *) +let accuser_publish_commitment_when_refutable node_ctxt ~other rollup + their_commitment their_commitment_hash = + let open Lwt_result_syntax in + when_ (Node_context.is_accuser node_ctxt) @@ fun () -> + (* We are seeing a commitment from someone else. We check if we agree + with it, otherwise the accuser publishes our commitment in order to + play the refutation game. *) + let* refutable = + is_refutable_commitment node_ctxt their_commitment their_commitment_hash + in + match refutable with + | None -> return_unit + | Some (our_commitment, our_commitment_hash) -> + let*! () = + Refutation_game_event.potential_conflict_detected + ~our_commitment_hash + ~their_commitment_hash + ~level:their_commitment.inbox_level + ~other + in + assert (Octez_smart_rollup.Address.(node_ctxt.rollup_address = rollup)) ; + Publisher.publish_single_commitment node_ctxt our_commitment + +(** Process an L1 SCORU operation (for the node's rollup) which is included for + the first time. {b Note}: this function does not process inboxes for the + rollup, which is done instead by {!Inbox.process_head}. *) +let process_included_l1_operation (type kind) (node_ctxt : Node_context.rw) + (head : Layer1.header) ~source (operation : kind manager_operation) + (result : kind successful_manager_operation_result) = + let open Lwt_result_syntax in + match (operation, result) with + | ( Sc_rollup_publish {commitment; _}, + Sc_rollup_publish_result {published_at_level; _} ) + when Node_context.is_operator node_ctxt source -> + (* Published commitment --------------------------------------------- *) + let save_lpc = + match Reference.get node_ctxt.lpc with + | None -> true + | Some lpc -> + Raw_level.to_int32 commitment.inbox_level >= lpc.inbox_level + in + let commitment = Sc_rollup_proto_types.Commitment.to_octez commitment in + if save_lpc then Reference.set node_ctxt.lpc (Some commitment) ; + let commitment_hash = Octez_smart_rollup.Commitment.hash commitment in + let* () = + Node_context.set_commitment_published_at_level + node_ctxt + commitment_hash + { + first_published_at_level = Raw_level.to_int32 published_at_level; + published_at_level = Some head.Layer1.level; + } + in + let*! () = + Commitment_event.last_published_commitment_updated + commitment_hash + head.Layer1.level + in + return_unit + | ( Sc_rollup_publish {commitment = their_commitment; rollup}, + Sc_rollup_publish_result + {published_at_level; staked_hash = their_commitment_hash; _} ) -> + (* Commitment published by someone else *) + (* We first register the publication information *) + let their_commitment_hash = + Sc_rollup_proto_types.Commitment_hash.to_octez their_commitment_hash + in + let* known_commitment = + Node_context.commitment_exists node_ctxt their_commitment_hash + in + let* () = + if not known_commitment then return_unit + else + let* republication = + Node_context.commitment_was_published + node_ctxt + ~source:Anyone + their_commitment_hash + in + if republication then return_unit + else + let* () = + Node_context.set_commitment_published_at_level + node_ctxt + their_commitment_hash + { + first_published_at_level = + Raw_level.to_int32 published_at_level; + published_at_level = None; + } + in + return_unit + in + (* An accuser node will publish its commitment if the other one is + refutable. *) + let rollup = Sc_rollup_proto_types.Address.to_octez rollup in + let their_commitment = + Sc_rollup_proto_types.Commitment.to_octez their_commitment + in + accuser_publish_commitment_when_refutable + node_ctxt + ~other:source + rollup + their_commitment + their_commitment_hash + | Sc_rollup_cement {commitment; _}, Sc_rollup_cement_result {inbox_level; _} + -> + (* Cemented commitment ---------------------------------------------- *) + let inbox_level = Raw_level.to_int32 inbox_level in + let* inbox_block = + Node_context.get_l2_block_by_level node_ctxt inbox_level + in + let commitment_hash = + Sc_rollup_proto_types.Commitment_hash.to_octez commitment + in + let*? () = + (* We stop the node if we disagree with a cemented commitment *) + let our_commitment_hash = inbox_block.header.commitment_hash in + error_unless + (Option.equal + Octez_smart_rollup.Commitment.Hash.( = ) + our_commitment_hash + (Some commitment_hash)) + (Sc_rollup_node_errors.Disagree_with_cemented + {inbox_level; ours = our_commitment_hash; on_l1 = commitment_hash}) + in + let lcc = Reference.get node_ctxt.lcc in + let*! () = + if inbox_level > lcc.level then ( + Reference.set + node_ctxt.lcc + {commitment = commitment_hash; level = inbox_level} ; + Commitment_event.last_cemented_commitment_updated + commitment_hash + inbox_level) + else Lwt.return_unit + in + return_unit + | ( Sc_rollup_refute _, + Sc_rollup_refute_result {game_status = Ended end_status; _} ) + | ( Sc_rollup_timeout _, + Sc_rollup_timeout_result {game_status = Ended end_status; _} ) -> ( + match end_status with + | Loser {loser; reason} when Node_context.is_operator node_ctxt loser -> + let result = + match reason with + | Conflict_resolved -> Sc_rollup_node_errors.Conflict_resolved + | Timeout -> Timeout + in + tzfail (Sc_rollup_node_errors.Lost_game result) + | Loser _ -> + (* Other player lost *) + return_unit + | Draw -> + let stakers = + match operation with + | Sc_rollup_refute {opponent; _} -> [source; opponent] + | Sc_rollup_timeout {stakers = {alice; bob}; _} -> [alice; bob] + | _ -> assert false + in + fail_when + (List.exists (Node_context.is_operator node_ctxt) stakers) + (Sc_rollup_node_errors.Lost_game Draw)) + | Dal_publish_slot_header slot_header, Dal_publish_slot_header_result _ + when Node_context.dal_supported node_ctxt -> + let* () = + Node_context.save_slot_header + node_ctxt + ~published_in_block_hash:head.Layer1.hash + (Sc_rollup_proto_types.Dal.Slot_header.to_octez slot_header.header) + in + return_unit + | _, _ -> + (* Other manager operations *) + return_unit + +let process_l1_operation (type kind) node_ctxt (head : Layer1.header) ~source + (operation : kind manager_operation) + (result : kind Apply_results.manager_operation_result) = + let open Lwt_result_syntax in + let is_for_my_rollup : type kind. kind manager_operation -> bool = function + | Sc_rollup_add_messages _ -> true + | Sc_rollup_cement {rollup; _} + | Sc_rollup_publish {rollup; _} + | Sc_rollup_refute {rollup; _} + | Sc_rollup_timeout {rollup; _} + | Sc_rollup_execute_outbox_message {rollup; _} + | Sc_rollup_recover_bond {sc_rollup = rollup; staker = _} -> + Octez_smart_rollup.Address.( + Sc_rollup_proto_types.Address.to_octez rollup + = node_ctxt.Node_context.rollup_address) + | Dal_publish_slot_header _ -> true + | Reveal _ | Transaction _ | Origination _ | Delegation _ + | Update_consensus_key _ | Register_global_constant _ | Set_deposits_limit _ + | Increase_paid_storage _ | Tx_rollup_origination | Tx_rollup_submit_batch _ + | Tx_rollup_commit _ | Tx_rollup_return_bond _ + | Tx_rollup_finalize_commitment _ | Tx_rollup_remove_commitment _ + | Tx_rollup_rejection _ | Tx_rollup_dispatch_tickets _ | Transfer_ticket _ + | Sc_rollup_originate _ | Zk_rollup_origination _ | Zk_rollup_publish _ + | Zk_rollup_update _ -> + false + in + if not (is_for_my_rollup operation) then return_unit + else + (* Only look at operations that are for the node's rollup *) + let*! () = + match Sc_rollup_injector.injector_operation_of_manager operation with + | None -> Lwt.return_unit + | Some op -> + let status, errors = + match result with + | Applied _ -> (`Applied, None) + | Backtracked (_, e) -> + (`Backtracked, Option.map Environment.wrap_tztrace e) + | Failed (_, e) -> (`Failed, Some (Environment.wrap_tztrace e)) + | Skipped _ -> (`Skipped, None) + in + Daemon_event.included_operation ?errors status op + in + match result with + | Applied success_result -> + process_included_l1_operation + node_ctxt + head + ~source + operation + success_result + | _ -> + (* No action for non successful operations *) + return_unit + +let process_l1_block_operations node_ctxt (head : Layer1.header) = + let open Lwt_result_syntax in + let* block = + Layer1_helpers.fetch_tezos_block node_ctxt.Node_context.l1_ctxt head.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 node_ctxt head ~source operation result + in + let apply_internal (type kind) accu ~source:_ + (_operation : kind Apply_internal_results.internal_operation) + (_result : kind Apply_internal_results.internal_operation_result) = + accu + in + let* () = + Layer1_services.process_manager_operations + return_unit + block.operations + {apply; apply_internal} + in + return_unit diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/batcher_worker_types.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon_helpers.mli similarity index 73% rename from src/proto_018_Proxford/lib_sc_rollup_node/batcher_worker_types.mli rename to src/proto_016_PtMumbai/lib_sc_rollup_node/daemon_helpers.mli index fd6ba038ada187a063446a07044418a5b878cb0a..a30441c2b7b76a98965fead547763d7b8ad2859e 100644 --- a/src/proto_018_Proxford/lib_sc_rollup_node/batcher_worker_types.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon_helpers.mli @@ -1,7 +1,9 @@ (*****************************************************************************) (* *) (* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) +(* Copyright (c) 2023 Nomadic Labs, *) +(* Copyright (c) 2023 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"),*) @@ -23,21 +25,10 @@ (* *) (*****************************************************************************) -(** This module contains the parameters for the worker (see {!Worker}) used by - the batcher. *) +(** Ensure that the initial state hash of the PVM as defined by the rollup node + matches the one of the PVM on the L1 node. *) +val check_pvm_initial_state_hash : _ Node_context.t -> unit tzresult Lwt.t -module Request : sig - (** Type of requests accepted by the batcher worker. *) - type ('a, 'b) t = - | Register : string list -> (L2_message.hash list, error trace) t - (** Request to register new L2 messages in the queue. *) - | New_head : Layer1.head -> (unit, error trace) t - (** Request to handle a new L1 head. *) - - type view = View : _ t -> view - - include - Worker_intf.REQUEST - with type ('a, 'request_error) t := ('a, 'request_error) t - and type view := view -end +(** React to L1 operations included in a block of the chain. *) +val process_l1_block_operations : + Node_context.rw -> Layer1.header -> unit tzresult Lwt.t diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.ml index dbac01ee0f715c0b9f46561c60128150e5c71514..ebe28f13b10d380af15af13c4ce4faa999a73b01 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.ml @@ -248,6 +248,14 @@ let payloads_history_of_messages ~predecessor ~predecessor_timestamp messages = in payloads_history +let serialize_external_message msg = + Environment.wrap_tzresult + @@ + let open Result_syntax in + let open Sc_rollup.Inbox_message in + let+ msg = serialize @@ External msg in + unsafe_to_string msg + module Internal_for_tests = struct let process_messages node_ctxt ~is_first_block ~predecessor head messages = assert (not is_first_block) ; diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.mli index 7b8e851b547687e06d69f4c24e72183cb2f4262e..eca086324abdb90dc0d1223e7d647cc5950bd24e 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.mli @@ -86,6 +86,10 @@ val same_as_layer_1 : Octez_smart_rollup.Inbox.t -> unit tzresult Lwt.t +(** Serialize an external messages to the protocol representation. NOTE: this + adds a tag ['\001'] at the beginning. *) +val serialize_external_message : string -> string tzresult + (**/**) module Internal_for_tests : sig diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/rollup_node_plugin.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/rollup_node_plugin.ml index af7636f504dcbdb831999a50e1acfffc03eb3630..16ff802accefda3938a8c6fe2b5ec529b3a80337 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/rollup_node_plugin.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/rollup_node_plugin.ml @@ -32,15 +32,9 @@ module Plugin : Protocol_plugin_sig.S = struct module Interpreter = Interpreter module Publisher = Publisher module Refutation_coordinator = Refutation_coordinator - module Batcher = Batcher + module Batcher_constants = Batcher_constants module Layer1_helpers = Layer1_helpers - - module L1_processing = struct - let check_pvm_initial_state_hash = Daemon.check_initial_state_hash - - let process_l1_block_operations = Daemon.process_l1_block_operations - end - + module L1_processing = Daemon_helpers module Pvm = Pvm_plugin end diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/batcher.ml deleted file mode 100644 index e2eed2c345ebadf904558d3b09e0f812493f27e9..0000000000000000000000000000000000000000 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher.ml +++ /dev/null @@ -1,466 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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. *) -(* *) -(*****************************************************************************) - -open Protocol -open Alpha_context -open Batcher_worker_types -module Message_queue = Hash_queue.Make (L2_message.Hash) (L2_message) - -module Batcher_events = Batcher_events.Declare (struct - let worker_name = "batcher" -end) - -module L2_batched_message = struct - type t = {content : string; l1_hash : Injector.Inj_operation.hash} -end - -module Batched_messages = Hash_queue.Make (L2_message.Hash) (L2_batched_message) - -type status = Pending_batch | Batched of Injector.Inj_operation.hash - -(* Same as {!Configuration.batcher} with max_batch_size non optional. *) -type conf = { - simulate : bool; - min_batch_elements : int; - min_batch_size : int; - max_batch_elements : int; - max_batch_size : int; -} - -type state = { - node_ctxt : Node_context.ro; - signer : Signature.public_key_hash; - conf : conf; - messages : Message_queue.t; - batched : Batched_messages.t; - mutable simulation_ctxt : Simulation.t option; -} - -let message_size s = - (* Encoded as length of s on 4 bytes + s *) - 4 + String.length s - -let inject_batch state (l2_messages : L2_message.t list) = - let open Lwt_result_syntax in - let messages = List.map L2_message.content l2_messages in - let operation = L1_operation.Add_messages {messages} in - let+ l1_hash = - Injector.add_pending_operation ~source:state.signer operation - in - List.iter - (fun msg -> - let content = L2_message.content msg in - let hash = L2_message.hash msg in - Batched_messages.replace state.batched hash {content; l1_hash}) - l2_messages - -let inject_batches state = List.iter_es (inject_batch state) - -let get_batches state ~only_full = - let ( current_rev_batch, - current_batch_size, - current_batch_elements, - full_batches ) = - Message_queue.fold - (fun msg_hash - message - ( current_rev_batch, - current_batch_size, - current_batch_elements, - full_batches ) -> - let size = message_size (L2_message.content message) in - let new_batch_size = current_batch_size + size in - let new_batch_elements = current_batch_elements + 1 in - if - new_batch_size <= state.conf.max_batch_size - && new_batch_elements <= state.conf.max_batch_elements - then - (* We can add the message to the current batch because we are still - within the bounds. *) - ( (msg_hash, message) :: current_rev_batch, - new_batch_size, - new_batch_elements, - full_batches ) - else - (* The batch augmented with the message would be too big but it is - below the limit without it. We finalize the current batch and - create a new one for the message. NOTE: Messages in the queue are - always < [state.conf.max_batch_size] because {!on_register} only - accepts those. *) - let batch = List.rev current_rev_batch in - ([(msg_hash, message)], size, 1, batch :: full_batches)) - state.messages - ([], 0, 0, []) - in - let batches = - if - (not only_full) - || current_batch_size >= state.conf.min_batch_size - && current_batch_elements >= state.conf.min_batch_elements - then - (* We have enough to make a batch with the last non-full batch. *) - List.rev current_rev_batch :: full_batches - else full_batches - in - List.fold_left - (fun (batches, to_remove) -> function - | [] -> (batches, to_remove) - | batch -> - let msg_hashes, batch = List.split batch in - let to_remove = List.rev_append msg_hashes to_remove in - (batch :: batches, to_remove)) - ([], []) - batches - -let produce_batches state ~only_full = - let open Lwt_result_syntax in - let batches, to_remove = get_batches state ~only_full in - match batches with - | [] -> return_unit - | _ -> - let* () = inject_batches state batches in - let*! () = - Batcher_events.(emit batched) - (List.length batches, List.length to_remove) - in - List.iter - (fun tr_hash -> Message_queue.remove state.messages tr_hash) - to_remove ; - return_unit - -let simulate node_ctxt simulation_ctxt (messages : L2_message.t list) = - let open Lwt_result_syntax in - let*? ext_messages = - Environment.wrap_tzresult - @@ List.map_e - (fun m -> - let open Result_syntax in - let open Sc_rollup.Inbox_message in - let+ msg = serialize @@ External (L2_message.content m) in - unsafe_to_string msg) - messages - in - let+ simulation_ctxt, _ticks = - Simulation.simulate_messages node_ctxt simulation_ctxt ext_messages - in - simulation_ctxt - -let on_register state (messages : string list) = - let open Lwt_result_syntax in - let max_size_msg = - min - (Protocol.Constants_repr.sc_rollup_message_size_limit - + 4 (* We add 4 because [message_size] adds 4. *)) - state.conf.max_batch_size - in - let*? messages = - List.mapi_e - (fun i message -> - if message_size message > max_size_msg then - error_with "Message %d is too large (max size is %d)" i max_size_msg - else Ok (L2_message.make message)) - messages - in - let* () = - if not state.conf.simulate then return_unit - else - match state.simulation_ctxt with - | None -> failwith "Simulation context of batcher not initialized" - | Some simulation_ctxt -> - let+ simulation_ctxt = - simulate state.node_ctxt simulation_ctxt messages - in - state.simulation_ctxt <- Some simulation_ctxt - in - let*! () = Batcher_events.(emit queue) (List.length messages) in - let hashes = - List.map - (fun message -> - let msg_hash = L2_message.hash message in - Message_queue.replace state.messages msg_hash message ; - msg_hash) - messages - in - let+ () = produce_batches state ~only_full:true in - hashes - -let on_new_head state head = - let open Lwt_result_syntax in - (* Produce batches first *) - let* () = produce_batches state ~only_full:false in - let* simulation_ctxt = - Simulation.start_simulation ~reveal_map:None state.node_ctxt head - in - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4224 - Replay with simulation may be too expensive *) - let+ simulation_ctxt, failing = - if not state.conf.simulate then return (simulation_ctxt, []) - else - (* Re-simulate one by one *) - Message_queue.fold_es - (fun msg_hash msg (simulation_ctxt, failing) -> - let*! result = simulate state.node_ctxt simulation_ctxt [msg] in - match result with - | Ok simulation_ctxt -> return (simulation_ctxt, failing) - | Error _ -> return (simulation_ctxt, msg_hash :: failing)) - state.messages - (simulation_ctxt, []) - in - state.simulation_ctxt <- Some simulation_ctxt ; - (* Forget failing messages *) - List.iter (Message_queue.remove state.messages) failing - -(** Maximum size of an L2 batch in bytes that can fit in an operation of the - protocol. *) -let protocol_max_batch_size = - let open Protocol in - let open Alpha_context in - let empty_message_op : _ Operation.t = - let open Operation in - { - shell = {branch = Block_hash.zero}; - protocol_data = - { - signature = Some Signature.zero; - contents = - Single - (Manager_operation - { - source = Signature.Public_key_hash.zero; - fee = Tez.of_mutez_exn Int64.max_int; - counter = Manager_counter.Internal_for_tests.of_int max_int; - gas_limit = - Gas.Arith.integral_of_int_exn ((max_int - 1) / 1000); - storage_limit = Z.of_int max_int; - operation = Sc_rollup_add_messages {messages = [""]}; - }); - }; - } - in - Protocol.Constants_repr.max_operation_data_length - - Data_encoding.Binary.length - Operation.encoding - (Operation.pack empty_message_op) - -let init_batcher_state node_ctxt ~signer (conf : Configuration.batcher) = - let open Lwt_syntax in - let conf = - { - simulate = conf.simulate; - min_batch_elements = conf.min_batch_elements; - min_batch_size = conf.min_batch_size; - max_batch_elements = conf.max_batch_elements; - max_batch_size = - Option.value conf.max_batch_size ~default:protocol_max_batch_size; - } - in - return - { - node_ctxt; - signer; - conf; - messages = Message_queue.create 100_000 (* ~ 400MB *); - batched = Batched_messages.create 100_000 (* ~ 400MB *); - simulation_ctxt = None; - } - -module Types = struct - type nonrec state = state - - type parameters = { - node_ctxt : Node_context.ro; - signer : Signature.public_key_hash; - conf : Configuration.batcher; - } -end - -module Name = struct - (* We only have a single batcher in the node *) - type t = unit - - let encoding = Data_encoding.unit - - let base = Batcher_events.Worker.section @ ["worker"] - - let pp _ _ = () - - let equal () () = true -end - -module Worker = Worker.MakeSingle (Name) (Request) (Types) - -type worker = Worker.infinite Worker.queue Worker.t - -module Handlers = struct - type self = worker - - let on_request : - type r request_error. - worker -> (r, request_error) Request.t -> (r, request_error) result Lwt.t - = - fun w request -> - let state = Worker.state w in - match request with - | Request.Register messages -> - protect @@ fun () -> on_register state messages - | Request.New_head head -> protect @@ fun () -> on_new_head state head - - type launch_error = error trace - - let on_launch _w () Types.{node_ctxt; signer; conf} = - let open Lwt_result_syntax in - let*! state = init_batcher_state node_ctxt ~signer conf in - return state - - let on_error (type a b) _w st (r : (a, b) Request.t) (errs : b) : - unit tzresult Lwt.t = - let open Lwt_result_syntax in - let request_view = Request.view r in - let emit_and_return_errors errs = - let*! () = - Batcher_events.(emit Worker.request_failed) (request_view, st, errs) - in - return_unit - in - match r with - | Request.Register _ -> emit_and_return_errors errs - | Request.New_head _ -> emit_and_return_errors errs - - let on_completion _w r _ st = - match Request.view r with - | Request.View (Register _ | New_head _) -> - Batcher_events.(emit Worker.request_completed_debug) (Request.view r, st) - - let on_no_request _ = Lwt.return_unit - - let on_close _w = Lwt.return_unit -end - -let table = Worker.create_table Queue - -let worker_promise, worker_waker = Lwt.task () - -let check_batcher_config Configuration.{max_batch_size; _} = - match max_batch_size with - | Some m when m > protocol_max_batch_size -> - error_with - "batcher.max_batch_size must be smaller than %d" - protocol_max_batch_size - | _ -> Ok () - -let start conf ~signer node_ctxt = - let open Lwt_result_syntax in - let*? () = check_batcher_config conf in - let node_ctxt = Node_context.readonly node_ctxt in - let+ worker = - Worker.launch table () {node_ctxt; signer; conf} (module Handlers) - in - Lwt.wakeup worker_waker worker - -let init conf ~signer node_ctxt = - let open Lwt_result_syntax in - match Lwt.state worker_promise with - | Lwt.Return _ -> - (* Worker already started, nothing to do. *) - return_unit - | Lwt.Fail exn -> - (* Worker crashed, not recoverable. *) - fail [Sc_rollup_node_errors.No_batcher; Exn exn] - | Lwt.Sleep -> - (* Never started, start it. *) - start conf ~signer node_ctxt - -(* This is a batcher worker for a single scoru *) -let worker = - lazy - (match Lwt.state worker_promise with - | Lwt.Return worker -> ok worker - | Lwt.Fail _ | Lwt.Sleep -> error Sc_rollup_node_errors.No_batcher) - -let active () = - match Lwt.state worker_promise with - | Lwt.Return _ -> true - | Lwt.Fail _ | Lwt.Sleep -> false - -let find_message hash = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - Message_queue.find_opt state.messages hash - -let get_queue () = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - Message_queue.bindings state.messages - -let handle_request_error rq = - let open Lwt_syntax in - let* rq in - match rq with - | Ok res -> return_ok res - | Error (Worker.Request_error errs) -> Lwt.return_error errs - | Error (Closed None) -> Lwt.return_error [Worker_types.Terminated] - | Error (Closed (Some errs)) -> Lwt.return_error errs - | Error (Any exn) -> Lwt.return_error [Exn exn] - -let register_messages messages = - let open Lwt_result_syntax in - let*? w = Lazy.force worker in - Worker.Queue.push_request_and_wait w (Request.Register messages) - |> handle_request_error - -let new_head b = - let open Lwt_result_syntax in - let w = Lazy.force worker in - match w with - | Error _ -> - (* There is no batcher, nothing to do *) - return_unit - | Ok w -> - Worker.Queue.push_request_and_wait w (Request.New_head b) - |> handle_request_error - -let shutdown () = - let w = Lazy.force worker in - match w with - | Error _ -> - (* There is no batcher, nothing to do *) - Lwt.return_unit - | Ok w -> Worker.shutdown w - -let message_status state msg_hash = - match Message_queue.find_opt state.messages msg_hash with - | Some msg -> Some (Pending_batch, L2_message.content msg) - | None -> ( - match Batched_messages.find_opt state.batched msg_hash with - | Some {content; l1_hash} -> Some (Batched l1_hash, content) - | None -> None) - -let message_status msg_hash = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - message_status state msg_hash diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_constants.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_constants.ml new file mode 100644 index 0000000000000000000000000000000000000000..edfa3ea32f006f8995fb2ca4999e28d87e98df71 --- /dev/null +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_constants.ml @@ -0,0 +1,56 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +let message_size_limit = Protocol.Constants_repr.sc_rollup_message_size_limit + +let protocol_max_batch_size = + let open Protocol in + let open Alpha_context in + let empty_message_op : _ Operation.t = + let open Operation in + { + shell = {branch = Block_hash.zero}; + protocol_data = + { + signature = Some Signature.zero; + contents = + Single + (Manager_operation + { + source = Signature.Public_key_hash.zero; + fee = Tez.of_mutez_exn Int64.max_int; + counter = Manager_counter.Internal_for_tests.of_int max_int; + gas_limit = + Gas.Arith.integral_of_int_exn ((max_int - 1) / 1000); + storage_limit = Z.of_int max_int; + operation = Sc_rollup_add_messages {messages = [""]}; + }); + }; + } + in + Protocol.Constants_repr.max_operation_data_length + - Data_encoding.Binary.length + Operation.encoding + (Operation.pack empty_message_op) diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_worker_types.mli b/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_constants.mli similarity index 73% rename from src/proto_017_PtNairob/lib_sc_rollup_node/batcher_worker_types.mli rename to src/proto_017_PtNairob/lib_sc_rollup_node/batcher_constants.mli index fd6ba038ada187a063446a07044418a5b878cb0a..dda41ddc6be1ad185be0997f1f289daae059f0c2 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_worker_types.mli +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_constants.mli @@ -1,7 +1,7 @@ (*****************************************************************************) (* *) (* 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"),*) @@ -23,21 +23,10 @@ (* *) (*****************************************************************************) -(** This module contains the parameters for the worker (see {!Worker}) used by - the batcher. *) +(** Maximum size of an L2 message allowed by the prototcol. Is + {!val:Protocol.Constants_repr.sc_rollup_message_size_limit}. *) +val message_size_limit : int -module Request : sig - (** Type of requests accepted by the batcher worker. *) - type ('a, 'b) t = - | Register : string list -> (L2_message.hash list, error trace) t - (** Request to register new L2 messages in the queue. *) - | New_head : Layer1.head -> (unit, error trace) t - (** Request to handle a new L1 head. *) - - type view = View : _ t -> view - - include - Worker_intf.REQUEST - with type ('a, 'request_error) t := ('a, 'request_error) t - and type view := view -end +(** Maximum size in bytes of an batch of L2 messages that can fit in an + operation on L1. It is protocol dependent. *) +val protocol_max_batch_size : int diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_events.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_events.ml deleted file mode 100644 index abe012a2dd0d266f6970b8f12980736f4f22a7c3..0000000000000000000000000000000000000000 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/batcher_events.ml +++ /dev/null @@ -1,91 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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 Declare (WORKER : sig - val worker_name : string -end) = -struct - include Internal_event.Simple - - let section = [Protocol.name; "sc_rollup_node"; WORKER.worker_name] - - let queue = - declare_1 - ~section - ~name:"queue" - ~msg:"adding {nb_messages} to queue" - ~level:Notice - ("nb_messages", Data_encoding.int31) - - let batched = - declare_2 - ~section - ~name:"batched" - ~msg:"batched {nb_messages} messages into {nb_batches} batches" - ~level:Notice - ("nb_batches", Data_encoding.int31) - ("nb_messages", Data_encoding.int31) - - module Worker = struct - open Batcher_worker_types - - let section = section @ ["worker"] - - let request_failed = - declare_3 - ~section - ~name:"request_failed" - ~msg:"request {view} failed ({worker_status}): {errors}" - ~level:Warning - ("view", Request.encoding) - ~pp1:Request.pp - ("worker_status", Worker_types.request_status_encoding) - ~pp2:Worker_types.pp_status - ("errors", Error_monad.trace_encoding) - ~pp3:Error_monad.pp_print_trace - - let request_completed_notice = - declare_2 - ~section - ~name:"request_completed_notice" - ~msg:"{view} {worker_status}" - ~level:Notice - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - - let request_completed_debug = - declare_2 - ~section - ~name:"request_completed_debug" - ~msg:"{view} {worker_status}" - ~level:Debug - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - end -end diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml index 0320a9683f18fb5dd20f2caea286a5bea7d2d0aa..2824fc471aa0fd83af851d751fd9a918bb56d288 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml @@ -25,284 +25,7 @@ (* *) (*****************************************************************************) -open Protocol -open Alpha_context -open Apply_results - -(** Returns [Some c] if [their_commitment] is refutable where [c] is our - commitment for the same inbox level. *) -let is_refutable_commitment node_ctxt - (their_commitment : Octez_smart_rollup.Commitment.t) their_commitment_hash = - let open Lwt_result_syntax in - let* l2_block = - Node_context.get_l2_block_by_level node_ctxt their_commitment.inbox_level - in - let* our_commitment_and_hash = - Option.filter_map_es - (fun hash -> - let+ commitment = Node_context.find_commitment node_ctxt hash in - Option.map (fun c -> (c, hash)) commitment) - l2_block.header.commitment_hash - in - match our_commitment_and_hash with - | Some (our_commitment, our_commitment_hash) - when Octez_smart_rollup.Commitment.Hash.( - their_commitment_hash <> our_commitment_hash - && their_commitment.predecessor = our_commitment.predecessor) -> - return our_commitment_and_hash - | _ -> return_none - -(** Publish a commitment when an accuser node sees a refutable commitment. *) -let accuser_publish_commitment_when_refutable node_ctxt ~other rollup - their_commitment their_commitment_hash = - let open Lwt_result_syntax in - when_ (Node_context.is_accuser node_ctxt) @@ fun () -> - (* We are seeing a commitment from someone else. We check if we agree - with it, otherwise the accuser publishes our commitment in order to - play the refutation game. *) - let* refutable = - is_refutable_commitment node_ctxt their_commitment their_commitment_hash - in - match refutable with - | None -> return_unit - | Some (our_commitment, our_commitment_hash) -> - let*! () = - Refutation_game_event.potential_conflict_detected - ~our_commitment_hash - ~their_commitment_hash - ~level:their_commitment.inbox_level - ~other - in - assert (Octez_smart_rollup.Address.(node_ctxt.rollup_address = rollup)) ; - Publisher.publish_single_commitment node_ctxt our_commitment - -(** Process an L1 SCORU operation (for the node's rollup) which is included - for the first time. {b Note}: this function does not process inboxes for - the rollup, which is done instead by {!Inbox.process_head}. *) -let process_included_l1_operation (type kind) (node_ctxt : Node_context.rw) - (head : Layer1.header) ~source (operation : kind manager_operation) - (result : kind successful_manager_operation_result) = - let open Lwt_result_syntax in - match (operation, result) with - | ( Sc_rollup_publish {commitment; _}, - Sc_rollup_publish_result {published_at_level; _} ) - when Node_context.is_operator node_ctxt source -> - (* Published commitment --------------------------------------------- *) - let save_lpc = - match Reference.get node_ctxt.lpc with - | None -> true - | Some lpc -> - Raw_level.to_int32 commitment.inbox_level >= lpc.inbox_level - in - let commitment = Sc_rollup_proto_types.Commitment.to_octez commitment in - if save_lpc then Reference.set node_ctxt.lpc (Some commitment) ; - let commitment_hash = Octez_smart_rollup.Commitment.hash commitment in - let* () = - Node_context.set_commitment_published_at_level - node_ctxt - commitment_hash - { - first_published_at_level = Raw_level.to_int32 published_at_level; - published_at_level = Some head.Layer1.level; - } - in - let*! () = - Commitment_event.last_published_commitment_updated - commitment_hash - head.Layer1.level - in - return_unit - | ( Sc_rollup_publish {commitment = their_commitment; rollup}, - Sc_rollup_publish_result - {published_at_level; staked_hash = their_commitment_hash; _} ) -> - (* Commitment published by someone else *) - (* We first register the publication information *) - let their_commitment_hash = - Sc_rollup_proto_types.Commitment_hash.to_octez their_commitment_hash - in - let* known_commitment = - Node_context.commitment_exists node_ctxt their_commitment_hash - in - let* () = - if not known_commitment then return_unit - else - let* republication = - Node_context.commitment_was_published - node_ctxt - ~source:Anyone - their_commitment_hash - in - if republication then return_unit - else - let* () = - Node_context.set_commitment_published_at_level - node_ctxt - their_commitment_hash - { - first_published_at_level = - Raw_level.to_int32 published_at_level; - published_at_level = None; - } - in - return_unit - in - (* An accuser node will publish its commitment if the other one is - refutable. *) - let rollup = Sc_rollup_proto_types.Address.to_octez rollup in - let their_commitment = - Sc_rollup_proto_types.Commitment.to_octez their_commitment - in - accuser_publish_commitment_when_refutable - node_ctxt - ~other:source - rollup - their_commitment - their_commitment_hash - | ( Sc_rollup_cement _, - Sc_rollup_cement_result {inbox_level; commitment_hash; _} ) -> - (* Cemented commitment ---------------------------------------------- *) - let inbox_level = Raw_level.to_int32 inbox_level in - let commitment_hash = - Sc_rollup_proto_types.Commitment_hash.to_octez commitment_hash - in - let* inbox_block = - Node_context.get_l2_block_by_level node_ctxt inbox_level - in - let*? () = - (* We stop the node if we disagree with a cemented commitment *) - let our_commitment_hash = inbox_block.header.commitment_hash in - error_unless - (Option.equal - Octez_smart_rollup.Commitment.Hash.( = ) - our_commitment_hash - (Some commitment_hash)) - (Sc_rollup_node_errors.Disagree_with_cemented - {inbox_level; ours = our_commitment_hash; on_l1 = commitment_hash}) - in - let lcc = Reference.get node_ctxt.lcc in - let*! () = - if inbox_level > lcc.level then ( - Reference.set - node_ctxt.lcc - {commitment = commitment_hash; level = inbox_level} ; - Commitment_event.last_cemented_commitment_updated - commitment_hash - inbox_level) - else Lwt.return_unit - in - return_unit - | ( Sc_rollup_refute _, - Sc_rollup_refute_result {game_status = Ended end_status; _} ) - | ( Sc_rollup_timeout _, - Sc_rollup_timeout_result {game_status = Ended end_status; _} ) -> ( - match end_status with - | Loser {loser; reason} when Node_context.is_operator node_ctxt loser -> - let result = - match reason with - | Conflict_resolved -> Sc_rollup_node_errors.Conflict_resolved - | Timeout -> Timeout - in - tzfail (Sc_rollup_node_errors.Lost_game result) - | Loser _ -> - (* Other player lost *) - return_unit - | Draw -> - let stakers = - match operation with - | Sc_rollup_refute {opponent; _} -> [source; opponent] - | Sc_rollup_timeout {stakers = {alice; bob}; _} -> [alice; bob] - | _ -> assert false - in - fail_when - (List.exists (Node_context.is_operator node_ctxt) stakers) - (Sc_rollup_node_errors.Lost_game Draw)) - | Dal_publish_slot_header _, Dal_publish_slot_header_result {slot_header; _} - when Node_context.dal_supported node_ctxt -> - let* () = - Node_context.save_slot_header - node_ctxt - ~published_in_block_hash:head.Layer1.hash - (Sc_rollup_proto_types.Dal.Slot_header.to_octez slot_header) - in - return_unit - | _, _ -> - (* Other manager operations *) - return_unit - -let process_l1_operation (type kind) node_ctxt (head : Layer1.header) ~source - (operation : kind manager_operation) - (result : kind Apply_results.manager_operation_result) = - let open Lwt_result_syntax in - let is_for_my_rollup : type kind. kind manager_operation -> bool = function - | Sc_rollup_add_messages _ -> true - | Sc_rollup_cement {rollup; _} - | Sc_rollup_publish {rollup; _} - | Sc_rollup_refute {rollup; _} - | Sc_rollup_timeout {rollup; _} - | Sc_rollup_execute_outbox_message {rollup; _} - | Sc_rollup_recover_bond {sc_rollup = rollup; staker = _} -> - Octez_smart_rollup.Address.( - Sc_rollup_proto_types.Address.to_octez rollup - = node_ctxt.Node_context.rollup_address) - | Dal_publish_slot_header _ -> true - | Reveal _ | Transaction _ | Origination _ | Delegation _ - | Update_consensus_key _ | Register_global_constant _ | Set_deposits_limit _ - | Increase_paid_storage _ | Transfer_ticket _ | Sc_rollup_originate _ - | Zk_rollup_origination _ | Zk_rollup_publish _ | Zk_rollup_update _ -> - false - in - if not (is_for_my_rollup operation) then return_unit - else - (* Only look at operations that are for the node's rollup *) - let*! () = - match Sc_rollup_injector.injector_operation_of_manager operation with - | None -> Lwt.return_unit - | Some op -> - let status, errors = - match result with - | Applied _ -> (`Applied, None) - | Backtracked (_, e) -> - (`Backtracked, Option.map Environment.wrap_tztrace e) - | Failed (_, e) -> (`Failed, Some (Environment.wrap_tztrace e)) - | Skipped _ -> (`Skipped, None) - in - Daemon_event.included_operation ?errors status op - in - match result with - | Applied success_result -> - process_included_l1_operation - node_ctxt - head - ~source - operation - success_result - | _ -> - (* No action for non successful operations *) - return_unit - -let process_l1_block_operations node_ctxt (head : Layer1.header) = - let open Lwt_result_syntax in - let* block = - Layer1_helpers.fetch_tezos_block node_ctxt.Node_context.l1_ctxt head.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 node_ctxt head ~source operation result - in - let apply_internal (type kind) accu ~source:_ - (_operation : kind Apply_internal_results.internal_operation) - (_result : kind Apply_internal_results.internal_operation_result) = - accu - in - let* () = - Layer1_services.process_manager_operations - return_unit - block.operations - {apply; apply_internal} - in - return_unit +open Daemon_helpers let before_origination (node_ctxt : _ Node_context.t) (header : Layer1.header) = let origination_level = node_ctxt.genesis_info.level in @@ -557,37 +280,11 @@ let install_finalizer (daemon_components : (module Daemon_components.S)) let* () = Event.shutdown_node exit_status in Tezos_base_unix.Internal_event_unix.close () -let check_initial_state_hash {Node_context.cctxt; rollup_address; kind; _} = - let open Lwt_result_syntax in - let module PVM = (val Pvm.of_kind kind) in - let* l1_reference_initial_state_hash = - RPC.Sc_rollup.initial_pvm_state_hash - (new Protocol_client_context.wrap_full cctxt) - (cctxt#chain, cctxt#block) - (Sc_rollup_proto_types.Address.of_octez rollup_address) - in - let*! s = PVM.initial_state ~empty:(PVM.State.empty ()) in - let*! l2_initial_state_hash = PVM.state_hash s in - let l1_reference_initial_state_hash = - Sc_rollup_proto_types.State_hash.to_octez l1_reference_initial_state_hash - in - let l2_initial_state_hash = - Sc_rollup_proto_types.State_hash.to_octez l2_initial_state_hash - in - fail_unless - Octez_smart_rollup.State_hash.( - l1_reference_initial_state_hash = l2_initial_state_hash) - (Sc_rollup_node_errors.Wrong_initial_pvm_state - { - initial_state_hash = l2_initial_state_hash; - expected_state_hash = l1_reference_initial_state_hash; - }) - let run node_ctxt configuration (daemon_components : (module Daemon_components.S)) = let open Lwt_result_syntax in let (module Components) = daemon_components in - let* () = check_initial_state_hash node_ctxt in + let* () = check_pvm_initial_state_hash node_ctxt in let* rpc_server = RPC_server.start node_ctxt configuration in let (_ : Lwt_exit.clean_up_callback_id) = install_finalizer daemon_components node_ctxt rpc_server @@ -761,7 +458,12 @@ module Internal_for_tests = struct end module Rollup_node_daemon_components : Daemon_components.S = struct - module Batcher = Batcher + module Batcher = struct + include Batcher + + let init c = init (module Rollup_node_plugin.Plugin) c + end + module RPC_server = RPC_server end diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/daemon_helpers.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon_helpers.ml new file mode 100644 index 0000000000000000000000000000000000000000..291b988c9598bf2c2fbdde3cb630a84be37720c8 --- /dev/null +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon_helpers.ml @@ -0,0 +1,331 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs, *) +(* Copyright (c) 2023 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context +open Apply_results + +let check_pvm_initial_state_hash {Node_context.cctxt; rollup_address; kind; _} = + let open Lwt_result_syntax in + let module PVM = (val Pvm.of_kind kind) in + let* l1_reference_initial_state_hash = + RPC.Sc_rollup.initial_pvm_state_hash + (new Protocol_client_context.wrap_full cctxt) + (cctxt#chain, cctxt#block) + (Sc_rollup_proto_types.Address.of_octez rollup_address) + in + let*! s = PVM.initial_state ~empty:(PVM.State.empty ()) in + let*! l2_initial_state_hash = PVM.state_hash s in + let l1_reference_initial_state_hash = + Sc_rollup_proto_types.State_hash.to_octez l1_reference_initial_state_hash + in + let l2_initial_state_hash = + Sc_rollup_proto_types.State_hash.to_octez l2_initial_state_hash + in + fail_unless + Octez_smart_rollup.State_hash.( + l1_reference_initial_state_hash = l2_initial_state_hash) + (Sc_rollup_node_errors.Wrong_initial_pvm_state + { + initial_state_hash = l2_initial_state_hash; + expected_state_hash = l1_reference_initial_state_hash; + }) + +(** Returns [Some c] if [their_commitment] is refutable where [c] is our + commitment for the same inbox level. *) +let is_refutable_commitment node_ctxt + (their_commitment : Octez_smart_rollup.Commitment.t) their_commitment_hash = + let open Lwt_result_syntax in + let* l2_block = + Node_context.get_l2_block_by_level node_ctxt their_commitment.inbox_level + in + let* our_commitment_and_hash = + Option.filter_map_es + (fun hash -> + let+ commitment = Node_context.find_commitment node_ctxt hash in + Option.map (fun c -> (c, hash)) commitment) + l2_block.header.commitment_hash + in + match our_commitment_and_hash with + | Some (our_commitment, our_commitment_hash) + when Octez_smart_rollup.Commitment.Hash.( + their_commitment_hash <> our_commitment_hash + && their_commitment.predecessor = our_commitment.predecessor) -> + return our_commitment_and_hash + | _ -> return_none + +(** Publish a commitment when an accuser node sees a refutable commitment. *) +let accuser_publish_commitment_when_refutable node_ctxt ~other rollup + their_commitment their_commitment_hash = + let open Lwt_result_syntax in + when_ (Node_context.is_accuser node_ctxt) @@ fun () -> + (* We are seeing a commitment from someone else. We check if we agree + with it, otherwise the accuser publishes our commitment in order to + play the refutation game. *) + let* refutable = + is_refutable_commitment node_ctxt their_commitment their_commitment_hash + in + match refutable with + | None -> return_unit + | Some (our_commitment, our_commitment_hash) -> + let*! () = + Refutation_game_event.potential_conflict_detected + ~our_commitment_hash + ~their_commitment_hash + ~level:their_commitment.inbox_level + ~other + in + assert (Octez_smart_rollup.Address.(node_ctxt.rollup_address = rollup)) ; + Publisher.publish_single_commitment node_ctxt our_commitment + +(** Process an L1 SCORU operation (for the node's rollup) which is included + for the first time. {b Note}: this function does not process inboxes for + the rollup, which is done instead by {!Inbox.process_head}. *) +let process_included_l1_operation (type kind) (node_ctxt : Node_context.rw) + (head : Layer1.header) ~source (operation : kind manager_operation) + (result : kind successful_manager_operation_result) = + let open Lwt_result_syntax in + match (operation, result) with + | ( Sc_rollup_publish {commitment; _}, + Sc_rollup_publish_result {published_at_level; _} ) + when Node_context.is_operator node_ctxt source -> + (* Published commitment --------------------------------------------- *) + let save_lpc = + match Reference.get node_ctxt.lpc with + | None -> true + | Some lpc -> + Raw_level.to_int32 commitment.inbox_level >= lpc.inbox_level + in + let commitment = Sc_rollup_proto_types.Commitment.to_octez commitment in + if save_lpc then Reference.set node_ctxt.lpc (Some commitment) ; + let commitment_hash = Octez_smart_rollup.Commitment.hash commitment in + let* () = + Node_context.set_commitment_published_at_level + node_ctxt + commitment_hash + { + first_published_at_level = Raw_level.to_int32 published_at_level; + published_at_level = Some head.Layer1.level; + } + in + let*! () = + Commitment_event.last_published_commitment_updated + commitment_hash + head.Layer1.level + in + return_unit + | ( Sc_rollup_publish {commitment = their_commitment; rollup}, + Sc_rollup_publish_result + {published_at_level; staked_hash = their_commitment_hash; _} ) -> + (* Commitment published by someone else *) + (* We first register the publication information *) + let their_commitment_hash = + Sc_rollup_proto_types.Commitment_hash.to_octez their_commitment_hash + in + let* known_commitment = + Node_context.commitment_exists node_ctxt their_commitment_hash + in + let* () = + if not known_commitment then return_unit + else + let* republication = + Node_context.commitment_was_published + node_ctxt + ~source:Anyone + their_commitment_hash + in + if republication then return_unit + else + let* () = + Node_context.set_commitment_published_at_level + node_ctxt + their_commitment_hash + { + first_published_at_level = + Raw_level.to_int32 published_at_level; + published_at_level = None; + } + in + return_unit + in + (* An accuser node will publish its commitment if the other one is + refutable. *) + let rollup = Sc_rollup_proto_types.Address.to_octez rollup in + let their_commitment = + Sc_rollup_proto_types.Commitment.to_octez their_commitment + in + accuser_publish_commitment_when_refutable + node_ctxt + ~other:source + rollup + their_commitment + their_commitment_hash + | ( Sc_rollup_cement _, + Sc_rollup_cement_result {inbox_level; commitment_hash; _} ) -> + (* Cemented commitment ---------------------------------------------- *) + let inbox_level = Raw_level.to_int32 inbox_level in + let commitment_hash = + Sc_rollup_proto_types.Commitment_hash.to_octez commitment_hash + in + let* inbox_block = + Node_context.get_l2_block_by_level node_ctxt inbox_level + in + let*? () = + (* We stop the node if we disagree with a cemented commitment *) + let our_commitment_hash = inbox_block.header.commitment_hash in + error_unless + (Option.equal + Octez_smart_rollup.Commitment.Hash.( = ) + our_commitment_hash + (Some commitment_hash)) + (Sc_rollup_node_errors.Disagree_with_cemented + {inbox_level; ours = our_commitment_hash; on_l1 = commitment_hash}) + in + let lcc = Reference.get node_ctxt.lcc in + let*! () = + if inbox_level > lcc.level then ( + Reference.set + node_ctxt.lcc + {commitment = commitment_hash; level = inbox_level} ; + Commitment_event.last_cemented_commitment_updated + commitment_hash + inbox_level) + else Lwt.return_unit + in + return_unit + | ( Sc_rollup_refute _, + Sc_rollup_refute_result {game_status = Ended end_status; _} ) + | ( Sc_rollup_timeout _, + Sc_rollup_timeout_result {game_status = Ended end_status; _} ) -> ( + match end_status with + | Loser {loser; reason} when Node_context.is_operator node_ctxt loser -> + let result = + match reason with + | Conflict_resolved -> Sc_rollup_node_errors.Conflict_resolved + | Timeout -> Timeout + in + tzfail (Sc_rollup_node_errors.Lost_game result) + | Loser _ -> + (* Other player lost *) + return_unit + | Draw -> + let stakers = + match operation with + | Sc_rollup_refute {opponent; _} -> [source; opponent] + | Sc_rollup_timeout {stakers = {alice; bob}; _} -> [alice; bob] + | _ -> assert false + in + fail_when + (List.exists (Node_context.is_operator node_ctxt) stakers) + (Sc_rollup_node_errors.Lost_game Draw)) + | Dal_publish_slot_header _, Dal_publish_slot_header_result {slot_header; _} + when Node_context.dal_supported node_ctxt -> + let* () = + Node_context.save_slot_header + node_ctxt + ~published_in_block_hash:head.Layer1.hash + (Sc_rollup_proto_types.Dal.Slot_header.to_octez slot_header) + in + return_unit + | _, _ -> + (* Other manager operations *) + return_unit + +let process_l1_operation (type kind) node_ctxt (head : Layer1.header) ~source + (operation : kind manager_operation) + (result : kind Apply_results.manager_operation_result) = + let open Lwt_result_syntax in + let is_for_my_rollup : type kind. kind manager_operation -> bool = function + | Sc_rollup_add_messages _ -> true + | Sc_rollup_cement {rollup; _} + | Sc_rollup_publish {rollup; _} + | Sc_rollup_refute {rollup; _} + | Sc_rollup_timeout {rollup; _} + | Sc_rollup_execute_outbox_message {rollup; _} + | Sc_rollup_recover_bond {sc_rollup = rollup; staker = _} -> + Octez_smart_rollup.Address.( + Sc_rollup_proto_types.Address.to_octez rollup + = node_ctxt.Node_context.rollup_address) + | Dal_publish_slot_header _ -> true + | Reveal _ | Transaction _ | Origination _ | Delegation _ + | Update_consensus_key _ | Register_global_constant _ | Set_deposits_limit _ + | Increase_paid_storage _ | Transfer_ticket _ | Sc_rollup_originate _ + | Zk_rollup_origination _ | Zk_rollup_publish _ | Zk_rollup_update _ -> + false + in + if not (is_for_my_rollup operation) then return_unit + else + (* Only look at operations that are for the node's rollup *) + let*! () = + match Sc_rollup_injector.injector_operation_of_manager operation with + | None -> Lwt.return_unit + | Some op -> + let status, errors = + match result with + | Applied _ -> (`Applied, None) + | Backtracked (_, e) -> + (`Backtracked, Option.map Environment.wrap_tztrace e) + | Failed (_, e) -> (`Failed, Some (Environment.wrap_tztrace e)) + | Skipped _ -> (`Skipped, None) + in + Daemon_event.included_operation ?errors status op + in + match result with + | Applied success_result -> + process_included_l1_operation + node_ctxt + head + ~source + operation + success_result + | _ -> + (* No action for non successful operations *) + return_unit + +let process_l1_block_operations node_ctxt (head : Layer1.header) = + let open Lwt_result_syntax in + let* block = + Layer1_helpers.fetch_tezos_block node_ctxt.Node_context.l1_ctxt head.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 node_ctxt head ~source operation result + in + let apply_internal (type kind) accu ~source:_ + (_operation : kind Apply_internal_results.internal_operation) + (_result : kind Apply_internal_results.internal_operation_result) = + accu + in + let* () = + Layer1_services.process_manager_operations + return_unit + block.operations + {apply; apply_internal} + in + return_unit diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/daemon_helpers.mli b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon_helpers.mli new file mode 100644 index 0000000000000000000000000000000000000000..a30441c2b7b76a98965fead547763d7b8ad2859e --- /dev/null +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon_helpers.mli @@ -0,0 +1,34 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs, *) +(* Copyright (c) 2023 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. *) +(* *) +(*****************************************************************************) + +(** Ensure that the initial state hash of the PVM as defined by the rollup node + matches the one of the PVM on the L1 node. *) +val check_pvm_initial_state_hash : _ Node_context.t -> unit tzresult Lwt.t + +(** React to L1 operations included in a block of the chain. *) +val process_l1_block_operations : + Node_context.rw -> Layer1.header -> unit tzresult Lwt.t diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.ml index 267bb98a99ae3d2e4976186cf9e51137bd1efe1c..4928e15a3fc00fc1bc4102fee636ec5da7f12e15 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.ml @@ -261,6 +261,14 @@ let payloads_history_of_messages ~is_first_block ~predecessor in payloads_history +let serialize_external_message msg = + Environment.wrap_tzresult + @@ + let open Result_syntax in + let open Sc_rollup.Inbox_message in + let+ msg = serialize @@ External msg in + unsafe_to_string msg + module Internal_for_tests = struct let process_messages = process_messages end diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.mli b/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.mli index 88a64b03897247443c53172954a87616d3979c12..392bc33a487baf37ff13f9f978e381b6786cd8a2 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.mli +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.mli @@ -90,6 +90,10 @@ val same_as_layer_1 : Octez_smart_rollup.Inbox.t -> unit tzresult Lwt.t +(** Serialize an external messages to the protocol representation. NOTE: this + adds a tag ['\001'] at the beginning. *) +val serialize_external_message : string -> string tzresult + (**/**) module Internal_for_tests : sig diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/rollup_node_plugin.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/rollup_node_plugin.ml index af7636f504dcbdb831999a50e1acfffc03eb3630..16ff802accefda3938a8c6fe2b5ec529b3a80337 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/rollup_node_plugin.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/rollup_node_plugin.ml @@ -32,15 +32,9 @@ module Plugin : Protocol_plugin_sig.S = struct module Interpreter = Interpreter module Publisher = Publisher module Refutation_coordinator = Refutation_coordinator - module Batcher = Batcher + module Batcher_constants = Batcher_constants module Layer1_helpers = Layer1_helpers - - module L1_processing = struct - let check_pvm_initial_state_hash = Daemon.check_initial_state_hash - - let process_l1_block_operations = Daemon.process_l1_block_operations - end - + module L1_processing = Daemon_helpers module Pvm = Pvm_plugin end diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/batcher.ml b/src/proto_018_Proxford/lib_sc_rollup_node/batcher.ml deleted file mode 100644 index 1e9f8967a9cb36c21ee5668657de66110b69a0af..0000000000000000000000000000000000000000 --- a/src/proto_018_Proxford/lib_sc_rollup_node/batcher.ml +++ /dev/null @@ -1,466 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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. *) -(* *) -(*****************************************************************************) - -open Protocol -open Alpha_context -open Batcher_worker_types -module Message_queue = Hash_queue.Make (L2_message.Hash) (L2_message) - -module Batcher_events = Batcher_events.Declare (struct - let worker_name = "batcher" -end) - -module L2_batched_message = struct - type t = {content : string; l1_hash : Injector.Inj_operation.hash} -end - -module Batched_messages = Hash_queue.Make (L2_message.Hash) (L2_batched_message) - -type status = Pending_batch | Batched of Injector.Inj_operation.hash - -(* Same as {!Configuration.batcher} with max_batch_size non optional. *) -type conf = { - simulate : bool; - min_batch_elements : int; - min_batch_size : int; - max_batch_elements : int; - max_batch_size : int; -} - -type state = { - node_ctxt : Node_context.ro; - signer : Signature.public_key_hash; - conf : conf; - messages : Message_queue.t; - batched : Batched_messages.t; - mutable simulation_ctxt : Simulation.t option; -} - -let message_size s = - (* Encoded as length of s on 4 bytes + s *) - 4 + String.length s - -let inject_batch state (l2_messages : L2_message.t list) = - let open Lwt_result_syntax in - let messages = List.map L2_message.content l2_messages in - let operation = L1_operation.Add_messages {messages} in - let+ l1_hash = - Injector.add_pending_operation ~source:state.signer operation - in - List.iter - (fun msg -> - let content = L2_message.content msg in - let hash = L2_message.hash msg in - Batched_messages.replace state.batched hash {content; l1_hash}) - l2_messages - -let inject_batches state = List.iter_es (inject_batch state) - -let get_batches state ~only_full = - let ( current_rev_batch, - current_batch_size, - current_batch_elements, - full_batches ) = - Message_queue.fold - (fun msg_hash - message - ( current_rev_batch, - current_batch_size, - current_batch_elements, - full_batches ) -> - let size = message_size (L2_message.content message) in - let new_batch_size = current_batch_size + size in - let new_batch_elements = current_batch_elements + 1 in - if - new_batch_size <= state.conf.max_batch_size - && new_batch_elements <= state.conf.max_batch_elements - then - (* We can add the message to the current batch because we are still - within the bounds. *) - ( (msg_hash, message) :: current_rev_batch, - new_batch_size, - new_batch_elements, - full_batches ) - else - (* The batch augmented with the message would be too big but it is - below the limit without it. We finalize the current batch and - create a new one for the message. NOTE: Messages in the queue are - always < [state.conf.max_batch_size] because {!on_register} only - accepts those. *) - let batch = List.rev current_rev_batch in - ([(msg_hash, message)], size, 1, batch :: full_batches)) - state.messages - ([], 0, 0, []) - in - let batches = - if - (not only_full) - || current_batch_size >= state.conf.min_batch_size - && current_batch_elements >= state.conf.min_batch_elements - then - (* We have enough to make a batch with the last non-full batch. *) - List.rev current_rev_batch :: full_batches - else full_batches - in - List.fold_left - (fun (batches, to_remove) -> function - | [] -> (batches, to_remove) - | batch -> - let msg_hashes, batch = List.split batch in - let to_remove = List.rev_append msg_hashes to_remove in - (batch :: batches, to_remove)) - ([], []) - batches - -let produce_batches state ~only_full = - let open Lwt_result_syntax in - let batches, to_remove = get_batches state ~only_full in - match batches with - | [] -> return_unit - | _ -> - let* () = inject_batches state batches in - let*! () = - Batcher_events.(emit batched) - (List.length batches, List.length to_remove) - in - List.iter - (fun tr_hash -> Message_queue.remove state.messages tr_hash) - to_remove ; - return_unit - -let simulate node_ctxt simulation_ctxt (messages : L2_message.t list) = - let open Lwt_result_syntax in - let*? ext_messages = - Environment.wrap_tzresult - @@ List.map_e - (fun m -> - let open Result_syntax in - let open Sc_rollup.Inbox_message in - let+ msg = serialize @@ External (L2_message.content m) in - unsafe_to_string msg) - messages - in - let+ simulation_ctxt, _ticks = - Simulation.simulate_messages node_ctxt simulation_ctxt ext_messages - in - simulation_ctxt - -let on_register state (messages : string list) = - let open Lwt_result_syntax in - let max_size_msg = - min - (Protocol.Constants_repr.sc_rollup_message_size_limit - + 4 (* We add 4 because [message_size] adds 4. *)) - state.conf.max_batch_size - in - let*? messages = - List.mapi_e - (fun i message -> - if message_size message > max_size_msg then - error_with "Message %d is too large (max size is %d)" i max_size_msg - else Ok (L2_message.make message)) - messages - in - let* () = - if not state.conf.simulate then return_unit - else - match state.simulation_ctxt with - | None -> failwith "Simulation context of batcher not initialized" - | Some simulation_ctxt -> - let+ simulation_ctxt = - simulate state.node_ctxt simulation_ctxt messages - in - state.simulation_ctxt <- Some simulation_ctxt - in - let*! () = Batcher_events.(emit queue) (List.length messages) in - let hashes = - List.map - (fun message -> - let msg_hash = L2_message.hash message in - Message_queue.replace state.messages msg_hash message ; - msg_hash) - messages - in - let+ () = produce_batches state ~only_full:true in - hashes - -let on_new_head state head = - let open Lwt_result_syntax in - (* Produce batches first *) - let* () = produce_batches state ~only_full:false in - let* simulation_ctxt = - Simulation.start_simulation ~reveal_map:None state.node_ctxt head - in - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4224 - Replay with simulation may be too expensive *) - let+ simulation_ctxt, failing = - if not state.conf.simulate then return (simulation_ctxt, []) - else - (* Re-simulate one by one *) - Message_queue.fold_es - (fun msg_hash msg (simulation_ctxt, failing) -> - let*! result = simulate state.node_ctxt simulation_ctxt [msg] in - match result with - | Ok simulation_ctxt -> return (simulation_ctxt, failing) - | Error _ -> return (simulation_ctxt, msg_hash :: failing)) - state.messages - (simulation_ctxt, []) - in - state.simulation_ctxt <- Some simulation_ctxt ; - (* Forget failing messages *) - List.iter (Message_queue.remove state.messages) failing - -(** Maximum size of an L2 batch in bytes that can fit in an operation of the - protocol. *) -let protocol_max_batch_size = - let open Protocol in - let open Alpha_context in - let empty_message_op : _ Operation.t = - let open Operation in - { - shell = {branch = Block_hash.zero}; - protocol_data = - { - signature = Some Signature.zero; - contents = - Single - (Manager_operation - { - source = Signature.Public_key_hash.zero; - fee = Tez.of_mutez_exn Int64.max_int; - counter = Manager_counter.Internal_for_tests.of_int max_int; - gas_limit = - Gas.Arith.integral_of_int_exn ((max_int - 1) / 1000); - storage_limit = Z.of_int max_int; - operation = Sc_rollup_add_messages {messages = [""]}; - }); - }; - } - in - Protocol.Constants_repr.max_operation_data_length - - Data_encoding.Binary.length - Operation.encoding_with_legacy_attestation_name - (Operation.pack empty_message_op) - -let init_batcher_state node_ctxt ~signer (conf : Configuration.batcher) = - let open Lwt_syntax in - let conf = - { - simulate = conf.simulate; - min_batch_elements = conf.min_batch_elements; - min_batch_size = conf.min_batch_size; - max_batch_elements = conf.max_batch_elements; - max_batch_size = - Option.value conf.max_batch_size ~default:protocol_max_batch_size; - } - in - return - { - node_ctxt; - signer; - conf; - messages = Message_queue.create 100_000 (* ~ 400MB *); - batched = Batched_messages.create 100_000 (* ~ 400MB *); - simulation_ctxt = None; - } - -module Types = struct - type nonrec state = state - - type parameters = { - node_ctxt : Node_context.ro; - signer : Signature.public_key_hash; - conf : Configuration.batcher; - } -end - -module Name = struct - (* We only have a single batcher in the node *) - type t = unit - - let encoding = Data_encoding.unit - - let base = Batcher_events.Worker.section @ ["worker"] - - let pp _ _ = () - - let equal () () = true -end - -module Worker = Worker.MakeSingle (Name) (Request) (Types) - -type worker = Worker.infinite Worker.queue Worker.t - -module Handlers = struct - type self = worker - - let on_request : - type r request_error. - worker -> (r, request_error) Request.t -> (r, request_error) result Lwt.t - = - fun w request -> - let state = Worker.state w in - match request with - | Request.Register messages -> - protect @@ fun () -> on_register state messages - | Request.New_head head -> protect @@ fun () -> on_new_head state head - - type launch_error = error trace - - let on_launch _w () Types.{node_ctxt; signer; conf} = - let open Lwt_result_syntax in - let*! state = init_batcher_state node_ctxt ~signer conf in - return state - - let on_error (type a b) _w st (r : (a, b) Request.t) (errs : b) : - unit tzresult Lwt.t = - let open Lwt_result_syntax in - let request_view = Request.view r in - let emit_and_return_errors errs = - let*! () = - Batcher_events.(emit Worker.request_failed) (request_view, st, errs) - in - return_unit - in - match r with - | Request.Register _ -> emit_and_return_errors errs - | Request.New_head _ -> emit_and_return_errors errs - - let on_completion _w r _ st = - match Request.view r with - | Request.View (Register _ | New_head _) -> - Batcher_events.(emit Worker.request_completed_debug) (Request.view r, st) - - let on_no_request _ = Lwt.return_unit - - let on_close _w = Lwt.return_unit -end - -let table = Worker.create_table Queue - -let worker_promise, worker_waker = Lwt.task () - -let check_batcher_config Configuration.{max_batch_size; _} = - match max_batch_size with - | Some m when m > protocol_max_batch_size -> - error_with - "batcher.max_batch_size must be smaller than %d" - protocol_max_batch_size - | _ -> Ok () - -let start conf ~signer node_ctxt = - let open Lwt_result_syntax in - let*? () = check_batcher_config conf in - let node_ctxt = Node_context.readonly node_ctxt in - let+ worker = - Worker.launch table () {node_ctxt; signer; conf} (module Handlers) - in - Lwt.wakeup worker_waker worker - -let init conf ~signer node_ctxt = - let open Lwt_result_syntax in - match Lwt.state worker_promise with - | Lwt.Return _ -> - (* Worker already started, nothing to do. *) - return_unit - | Lwt.Fail exn -> - (* Worker crashed, not recoverable. *) - fail [Sc_rollup_node_errors.No_batcher; Exn exn] - | Lwt.Sleep -> - (* Never started, start it. *) - start conf ~signer node_ctxt - -(* This is a batcher worker for a single scoru *) -let worker = - lazy - (match Lwt.state worker_promise with - | Lwt.Return worker -> ok worker - | Lwt.Fail _ | Lwt.Sleep -> error Sc_rollup_node_errors.No_batcher) - -let active () = - match Lwt.state worker_promise with - | Lwt.Return _ -> true - | Lwt.Fail _ | Lwt.Sleep -> false - -let find_message hash = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - Message_queue.find_opt state.messages hash - -let get_queue () = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - Message_queue.bindings state.messages - -let handle_request_error rq = - let open Lwt_syntax in - let* rq in - match rq with - | Ok res -> return_ok res - | Error (Worker.Request_error errs) -> Lwt.return_error errs - | Error (Closed None) -> Lwt.return_error [Worker_types.Terminated] - | Error (Closed (Some errs)) -> Lwt.return_error errs - | Error (Any exn) -> Lwt.return_error [Exn exn] - -let register_messages messages = - let open Lwt_result_syntax in - let*? w = Lazy.force worker in - Worker.Queue.push_request_and_wait w (Request.Register messages) - |> handle_request_error - -let new_head b = - let open Lwt_result_syntax in - let w = Lazy.force worker in - match w with - | Error _ -> - (* There is no batcher, nothing to do *) - return_unit - | Ok w -> - Worker.Queue.push_request_and_wait w (Request.New_head b) - |> handle_request_error - -let shutdown () = - let w = Lazy.force worker in - match w with - | Error _ -> - (* There is no batcher, nothing to do *) - Lwt.return_unit - | Ok w -> Worker.shutdown w - -let message_status state msg_hash = - match Message_queue.find_opt state.messages msg_hash with - | Some msg -> Some (Pending_batch, L2_message.content msg) - | None -> ( - match Batched_messages.find_opt state.batched msg_hash with - | Some {content; l1_hash} -> Some (Batched l1_hash, content) - | None -> None) - -let message_status msg_hash = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - message_status state msg_hash diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/batcher.mli b/src/proto_018_Proxford/lib_sc_rollup_node/batcher.mli deleted file mode 100644 index f38610bade8e27745ce4bdab2d7f0bfdb09ce572..0000000000000000000000000000000000000000 --- a/src/proto_018_Proxford/lib_sc_rollup_node/batcher.mli +++ /dev/null @@ -1,55 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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. *) -(* *) -(*****************************************************************************) - -include Daemon_components.Batcher_sig - -(** The type for the status of messages in the batcher. *) -type status = - | Pending_batch (** The message is in the queue of the batcher. *) - | Batched of Injector.Inj_operation.hash - (** The message has already been batched and sent to the injector in an L1 - operation whose hash is given. *) - -(** Return [true] if the batcher was started for this node. *) -val active : unit -> bool - -(** Retrieve an L2 message from the queue. *) -val find_message : L2_message.hash -> L2_message.t option tzresult - -(** List all queued messages in the order they appear in the queue, i.e. the - message that were added first to the queue are at the end of list. *) -val get_queue : unit -> (L2_message.hash * L2_message.t) list tzresult - -(** [register_messages messages] registers new L2 [messages] in the queue of the - batcher for future injection on L1. If the batcher was initialized with - [simualte = true], the messages are evaluated the batcher's incremental - simulation context. In this case, when the application fails, the messages - are not queued. *) -val register_messages : string list -> L2_message.hash list tzresult Lwt.t - -(** The status of a message in the batcher. Returns [None] if the message is not - known by the batcher (the batcher only keeps the batched status of the last - 500000 messages). *) -val message_status : L2_message.hash -> (status * string) option tzresult diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/batcher_constants.ml b/src/proto_018_Proxford/lib_sc_rollup_node/batcher_constants.ml new file mode 100644 index 0000000000000000000000000000000000000000..c18ba0722bac0ea1bf201d27a4f927c88b5d0272 --- /dev/null +++ b/src/proto_018_Proxford/lib_sc_rollup_node/batcher_constants.ml @@ -0,0 +1,56 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +let message_size_limit = Protocol.Constants_repr.sc_rollup_message_size_limit + +let protocol_max_batch_size = + let open Protocol in + let open Alpha_context in + let empty_message_op : _ Operation.t = + let open Operation in + { + shell = {branch = Block_hash.zero}; + protocol_data = + { + signature = Some Signature.zero; + contents = + Single + (Manager_operation + { + source = Signature.Public_key_hash.zero; + fee = Tez.of_mutez_exn Int64.max_int; + counter = Manager_counter.Internal_for_tests.of_int max_int; + gas_limit = + Gas.Arith.integral_of_int_exn ((max_int - 1) / 1000); + storage_limit = Z.of_int max_int; + operation = Sc_rollup_add_messages {messages = [""]}; + }); + }; + } + in + Protocol.Constants_repr.max_operation_data_length + - Data_encoding.Binary.length + Operation.encoding_with_legacy_attestation_name + (Operation.pack empty_message_op) diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/batcher_constants.mli b/src/proto_018_Proxford/lib_sc_rollup_node/batcher_constants.mli new file mode 100644 index 0000000000000000000000000000000000000000..dda41ddc6be1ad185be0997f1f289daae059f0c2 --- /dev/null +++ b/src/proto_018_Proxford/lib_sc_rollup_node/batcher_constants.mli @@ -0,0 +1,32 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Maximum size of an L2 message allowed by the prototcol. Is + {!val:Protocol.Constants_repr.sc_rollup_message_size_limit}. *) +val message_size_limit : int + +(** Maximum size in bytes of an batch of L2 messages that can fit in an + operation on L1. It is protocol dependent. *) +val protocol_max_batch_size : int diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/batcher_worker_types.ml b/src/proto_018_Proxford/lib_sc_rollup_node/batcher_worker_types.ml deleted file mode 100644 index f7ab4535c26b164841fd61fb10f6d4dd0134ad54..0000000000000000000000000000000000000000 --- a/src/proto_018_Proxford/lib_sc_rollup_node/batcher_worker_types.ml +++ /dev/null @@ -1,69 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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 Request = struct - type ('a, 'b) t = - | Register : string list -> (L2_message.hash list, error trace) t - | New_head : Layer1.head -> (unit, error trace) t - - type view = View : _ t -> view - - let view req = View req - - let encoding = - let open Data_encoding in - union - [ - case - (Tag 0) - ~title:"Register" - (obj2 - (req "request" (constant "register")) - (req "messages" (list L2_message.content_encoding))) - (function - | View (Register messages) -> Some ((), messages) | _ -> None) - (fun ((), messages) -> View (Register messages)); - case - (Tag 1) - ~title:"New_head" - (obj2 - (req "request" (constant "new_head")) - (req "block" Layer1.head_encoding)) - (function View (New_head b) -> Some ((), b) | _ -> None) - (fun ((), b) -> View (New_head b)); - ] - - let pp ppf (View r) = - match r with - | Register messages -> - Format.fprintf ppf "register %d new L2 message" (List.length messages) - | New_head {Layer1.hash; level} -> - Format.fprintf - ppf - "switching to new L1 head %a at level %ld" - Block_hash.pp - hash - level -end diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/daemon.ml b/src/proto_018_Proxford/lib_sc_rollup_node/daemon.ml index ce639a407cff766237c0dbd58539b48c9a127177..9d5d77f98a8ec4385882eff492ce6fbda9fa20ff 100644 --- a/src/proto_018_Proxford/lib_sc_rollup_node/daemon.ml +++ b/src/proto_018_Proxford/lib_sc_rollup_node/daemon.ml @@ -25,279 +25,7 @@ (* *) (*****************************************************************************) -open Protocol -open Alpha_context -open Apply_results - -(** Returns [Some c] if [their_commitment] is refutable where [c] is our - commitment for the same inbox level. *) -let is_refutable_commitment node_ctxt - (their_commitment : Sc_rollup.Commitment.t) their_commitment_hash = - let open Lwt_result_syntax in - let* l2_block = - Node_context.get_l2_block_by_level - node_ctxt - (Raw_level.to_int32 their_commitment.inbox_level) - in - let* our_commitment_and_hash = - Option.filter_map_es - (fun hash -> - let hash = Sc_rollup_proto_types.Commitment_hash.of_octez hash in - let+ commitment = Node_context.find_commitment node_ctxt hash in - Option.map (fun c -> (c, hash)) commitment) - l2_block.header.commitment_hash - in - match our_commitment_and_hash with - | Some (our_commitment, our_commitment_hash) - when Sc_rollup.Commitment.Hash.( - their_commitment_hash <> our_commitment_hash - && their_commitment.predecessor = our_commitment.predecessor) -> - return our_commitment_and_hash - | _ -> return_none - -(** Publish a commitment when an accuser node sees a refutable commitment. *) -let accuser_publish_commitment_when_refutable node_ctxt ~other rollup - their_commitment their_commitment_hash = - let open Lwt_result_syntax in - when_ (Node_context.is_accuser node_ctxt) @@ fun () -> - (* We are seeing a commitment from someone else. We check if we agree - with it, otherwise the accuser publishes our commitment in order to - play the refutation game. *) - let* refutable = - is_refutable_commitment node_ctxt their_commitment their_commitment_hash - in - match refutable with - | None -> return_unit - | Some (our_commitment, our_commitment_hash) -> - let*! () = - Refutation_game_event.potential_conflict_detected - ~our_commitment_hash - ~their_commitment_hash - ~level:their_commitment.inbox_level - ~other - in - assert (Sc_rollup.Address.(node_ctxt.rollup_address = rollup)) ; - Publisher.publish_single_commitment node_ctxt our_commitment - -(** Process an L1 SCORU operation (for the node's rollup) which is included - for the first time. {b Note}: this function does not process inboxes for - the rollup, which is done instead by {!Inbox.process_head}. *) -let process_included_l1_operation (type kind) (node_ctxt : Node_context.rw) - (head : Layer1.header) ~source (operation : kind manager_operation) - (result : kind successful_manager_operation_result) = - let open Lwt_result_syntax in - match (operation, result) with - | ( Sc_rollup_publish {commitment; _}, - Sc_rollup_publish_result {published_at_level; _} ) - when Node_context.is_operator node_ctxt source -> - (* Published commitment --------------------------------------------- *) - let save_lpc = - match Reference.get node_ctxt.lpc with - | None -> true - | Some lpc -> - Raw_level.to_int32 commitment.inbox_level >= lpc.inbox_level - in - let commitment = Sc_rollup_proto_types.Commitment.to_octez commitment in - if save_lpc then Reference.set node_ctxt.lpc (Some commitment) ; - let commitment_hash = Octez_smart_rollup.Commitment.hash commitment in - let* () = - Node_context.set_commitment_published_at_level - node_ctxt - commitment_hash - { - first_published_at_level = Raw_level.to_int32 published_at_level; - published_at_level = Some head.Layer1.level; - } - in - let*! () = - Commitment_event.last_published_commitment_updated - commitment_hash - head.Layer1.level - in - return_unit - | ( Sc_rollup_publish {commitment = their_commitment; rollup}, - Sc_rollup_publish_result - {published_at_level; staked_hash = their_commitment_hash; _} ) -> - (* Commitment published by someone else *) - (* We first register the publication information *) - let* known_commitment = - Node_context.commitment_exists node_ctxt their_commitment_hash - in - let* () = - if not known_commitment then return_unit - else - let* republication = - Node_context.commitment_was_published - node_ctxt - ~source:Anyone - their_commitment_hash - in - if republication then return_unit - else - let* () = - Node_context.set_commitment_published_at_level - node_ctxt - their_commitment_hash - { - first_published_at_level = - Raw_level.to_int32 published_at_level; - published_at_level = None; - } - in - return_unit - in - (* An accuser node will publish its commitment if the other one is - refutable. *) - accuser_publish_commitment_when_refutable - node_ctxt - ~other:source - rollup - their_commitment - their_commitment_hash - | ( Sc_rollup_cement _, - Sc_rollup_cement_result {inbox_level; commitment_hash; _} ) -> - (* Cemented commitment ---------------------------------------------- *) - let proto_commitment_hash = commitment_hash in - let inbox_level = Raw_level.to_int32 inbox_level in - let commitment_hash = - Sc_rollup_proto_types.Commitment_hash.to_octez commitment_hash - in - let* inbox_block = - Node_context.get_l2_block_by_level node_ctxt inbox_level - in - let*? () = - (* We stop the node if we disagree with a cemented commitment *) - let our_commitment_hash = inbox_block.header.commitment_hash in - error_unless - (Option.equal - Octez_smart_rollup.Commitment.Hash.( = ) - our_commitment_hash - (Some commitment_hash)) - (Sc_rollup_node_errors.Disagree_with_cemented - {inbox_level; ours = our_commitment_hash; on_l1 = commitment_hash}) - in - let lcc = Reference.get node_ctxt.lcc in - let*! () = - if inbox_level > lcc.level then ( - Reference.set - node_ctxt.lcc - {commitment = proto_commitment_hash; level = inbox_level} ; - Commitment_event.last_cemented_commitment_updated - proto_commitment_hash - inbox_level) - else Lwt.return_unit - in - return_unit - | ( Sc_rollup_refute _, - Sc_rollup_refute_result {game_status = Ended end_status; _} ) - | ( Sc_rollup_timeout _, - Sc_rollup_timeout_result {game_status = Ended end_status; _} ) -> ( - match end_status with - | Loser {loser; reason} when Node_context.is_operator node_ctxt loser -> - let result = - match reason with - | Conflict_resolved -> Sc_rollup_node_errors.Conflict_resolved - | Timeout -> Timeout - in - tzfail (Sc_rollup_node_errors.Lost_game result) - | Loser _ -> - (* Other player lost *) - return_unit - | Draw -> - let stakers = - match operation with - | Sc_rollup_refute {opponent; _} -> [source; opponent] - | Sc_rollup_timeout {stakers = {alice; bob}; _} -> [alice; bob] - | _ -> assert false - in - fail_when - (List.exists (Node_context.is_operator node_ctxt) stakers) - (Sc_rollup_node_errors.Lost_game Draw)) - | Dal_publish_slot_header _, Dal_publish_slot_header_result {slot_header; _} - when Node_context.dal_supported node_ctxt -> - let* () = - Node_context.save_slot_header - node_ctxt - ~published_in_block_hash:head.Layer1.hash - (Sc_rollup_proto_types.Dal.Slot_header.to_octez slot_header) - in - return_unit - | _, _ -> - (* Other manager operations *) - return_unit - -let process_l1_operation (type kind) node_ctxt (head : Layer1.header) ~source - (operation : kind manager_operation) - (result : kind Apply_results.manager_operation_result) = - let open Lwt_result_syntax in - let is_for_my_rollup : type kind. kind manager_operation -> bool = function - | Sc_rollup_add_messages _ -> true - | Sc_rollup_cement {rollup; _} - | Sc_rollup_publish {rollup; _} - | Sc_rollup_refute {rollup; _} - | Sc_rollup_timeout {rollup; _} - | Sc_rollup_execute_outbox_message {rollup; _} - | Sc_rollup_recover_bond {sc_rollup = rollup; staker = _} -> - Sc_rollup.Address.(rollup = node_ctxt.Node_context.rollup_address) - | Dal_publish_slot_header _ -> true - | Reveal _ | Transaction _ | Origination _ | Delegation _ - | Update_consensus_key _ | Register_global_constant _ - | Increase_paid_storage _ | Transfer_ticket _ | Sc_rollup_originate _ - | Zk_rollup_origination _ | Zk_rollup_publish _ | Zk_rollup_update _ -> - false - in - if not (is_for_my_rollup operation) then return_unit - else - (* Only look at operations that are for the node's rollup *) - let*! () = - match Sc_rollup_injector.injector_operation_of_manager operation with - | None -> Lwt.return_unit - | Some op -> - let status, errors = - match result with - | Applied _ -> (`Applied, None) - | Backtracked (_, e) -> - (`Backtracked, Option.map Environment.wrap_tztrace e) - | Failed (_, e) -> (`Failed, Some (Environment.wrap_tztrace e)) - | Skipped _ -> (`Skipped, None) - in - Daemon_event.included_operation ?errors status op - in - match result with - | Applied success_result -> - process_included_l1_operation - node_ctxt - head - ~source - operation - success_result - | _ -> - (* No action for non successful operations *) - return_unit - -let process_l1_block_operations node_ctxt (head : Layer1.header) = - let open Lwt_result_syntax in - let* block = - Layer1_helpers.fetch_tezos_block node_ctxt.Node_context.l1_ctxt head.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 node_ctxt head ~source operation result - in - let apply_internal (type kind) accu ~source:_ - (_operation : kind Apply_internal_results.internal_operation) - (_result : kind Apply_internal_results.internal_operation_result) = - accu - in - let* () = - Layer1_services.process_manager_operations - return_unit - block.operations - {apply; apply_internal} - in - return_unit +open Daemon_helpers let before_origination (node_ctxt : _ Node_context.t) (header : Layer1.header) = let origination_level = node_ctxt.genesis_info.level in @@ -563,37 +291,11 @@ let install_finalizer (daemon_components : (module Daemon_components.S)) let* () = Event.shutdown_node exit_status in Tezos_base_unix.Internal_event_unix.close () -let check_initial_state_hash {Node_context.cctxt; rollup_address; kind; _} = - let open Lwt_result_syntax in - let module PVM = (val Pvm.of_kind kind) in - let* l1_reference_initial_state_hash = - RPC.Sc_rollup.initial_pvm_state_hash - (new Protocol_client_context.wrap_full cctxt) - (cctxt#chain, cctxt#block) - rollup_address - in - let*! s = PVM.initial_state ~empty:(PVM.State.empty ()) in - let*! l2_initial_state_hash = PVM.state_hash s in - let l1_reference_initial_state_hash = - Sc_rollup_proto_types.State_hash.to_octez l1_reference_initial_state_hash - in - let l2_initial_state_hash = - Sc_rollup_proto_types.State_hash.to_octez l2_initial_state_hash - in - fail_unless - Octez_smart_rollup.State_hash.( - l1_reference_initial_state_hash = l2_initial_state_hash) - (Sc_rollup_node_errors.Wrong_initial_pvm_state - { - initial_state_hash = l2_initial_state_hash; - expected_state_hash = l1_reference_initial_state_hash; - }) - let run node_ctxt configuration (daemon_components : (module Daemon_components.S)) = let open Lwt_result_syntax in let (module Components) = daemon_components in - let* () = check_initial_state_hash node_ctxt in + let* () = check_pvm_initial_state_hash node_ctxt in let* rpc_server = RPC_server.start node_ctxt configuration in let (_ : Lwt_exit.clean_up_callback_id) = install_finalizer daemon_components node_ctxt rpc_server @@ -776,7 +478,12 @@ module Internal_for_tests = struct end module Rollup_node_daemon_components : Daemon_components.S = struct - module Batcher = Batcher + module Batcher = struct + include Batcher + + let init c = init (module Rollup_node_plugin.Plugin) c + end + module RPC_server = RPC_server end diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/daemon_helpers.ml b/src/proto_018_Proxford/lib_sc_rollup_node/daemon_helpers.ml new file mode 100644 index 0000000000000000000000000000000000000000..57315ced452c02e0f548959d783bc549d5c003a2 --- /dev/null +++ b/src/proto_018_Proxford/lib_sc_rollup_node/daemon_helpers.ml @@ -0,0 +1,326 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs, *) +(* Copyright (c) 2023 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context +open Apply_results + +let check_pvm_initial_state_hash {Node_context.cctxt; rollup_address; kind; _} = + let open Lwt_result_syntax in + let module PVM = (val Pvm.of_kind kind) in + let* l1_reference_initial_state_hash = + RPC.Sc_rollup.initial_pvm_state_hash + (new Protocol_client_context.wrap_full cctxt) + (cctxt#chain, cctxt#block) + rollup_address + in + let*! s = PVM.initial_state ~empty:(PVM.State.empty ()) in + let*! l2_initial_state_hash = PVM.state_hash s in + let l1_reference_initial_state_hash = + Sc_rollup_proto_types.State_hash.to_octez l1_reference_initial_state_hash + in + let l2_initial_state_hash = + Sc_rollup_proto_types.State_hash.to_octez l2_initial_state_hash + in + fail_unless + Octez_smart_rollup.State_hash.( + l1_reference_initial_state_hash = l2_initial_state_hash) + (Sc_rollup_node_errors.Wrong_initial_pvm_state + { + initial_state_hash = l2_initial_state_hash; + expected_state_hash = l1_reference_initial_state_hash; + }) + +(** Returns [Some c] if [their_commitment] is refutable where [c] is our + commitment for the same inbox level. *) +let is_refutable_commitment node_ctxt + (their_commitment : Sc_rollup.Commitment.t) their_commitment_hash = + let open Lwt_result_syntax in + let* l2_block = + Node_context.get_l2_block_by_level + node_ctxt + (Raw_level.to_int32 their_commitment.inbox_level) + in + let* our_commitment_and_hash = + Option.filter_map_es + (fun hash -> + let hash = Sc_rollup_proto_types.Commitment_hash.of_octez hash in + let+ commitment = Node_context.find_commitment node_ctxt hash in + Option.map (fun c -> (c, hash)) commitment) + l2_block.header.commitment_hash + in + match our_commitment_and_hash with + | Some (our_commitment, our_commitment_hash) + when Sc_rollup.Commitment.Hash.( + their_commitment_hash <> our_commitment_hash + && their_commitment.predecessor = our_commitment.predecessor) -> + return our_commitment_and_hash + | _ -> return_none + +(** Publish a commitment when an accuser node sees a refutable commitment. *) +let accuser_publish_commitment_when_refutable node_ctxt ~other rollup + their_commitment their_commitment_hash = + let open Lwt_result_syntax in + when_ (Node_context.is_accuser node_ctxt) @@ fun () -> + (* We are seeing a commitment from someone else. We check if we agree + with it, otherwise the accuser publishes our commitment in order to + play the refutation game. *) + let* refutable = + is_refutable_commitment node_ctxt their_commitment their_commitment_hash + in + match refutable with + | None -> return_unit + | Some (our_commitment, our_commitment_hash) -> + let*! () = + Refutation_game_event.potential_conflict_detected + ~our_commitment_hash + ~their_commitment_hash + ~level:their_commitment.inbox_level + ~other + in + assert (Sc_rollup.Address.(node_ctxt.rollup_address = rollup)) ; + Publisher.publish_single_commitment node_ctxt our_commitment + +(** Process an L1 SCORU operation (for the node's rollup) which is included + for the first time. {b Note}: this function does not process inboxes for + the rollup, which is done instead by {!Inbox.process_head}. *) +let process_included_l1_operation (type kind) (node_ctxt : Node_context.rw) + (head : Layer1.header) ~source (operation : kind manager_operation) + (result : kind successful_manager_operation_result) = + let open Lwt_result_syntax in + match (operation, result) with + | ( Sc_rollup_publish {commitment; _}, + Sc_rollup_publish_result {published_at_level; _} ) + when Node_context.is_operator node_ctxt source -> + (* Published commitment --------------------------------------------- *) + let save_lpc = + match Reference.get node_ctxt.lpc with + | None -> true + | Some lpc -> + Raw_level.to_int32 commitment.inbox_level >= lpc.inbox_level + in + let commitment = Sc_rollup_proto_types.Commitment.to_octez commitment in + if save_lpc then Reference.set node_ctxt.lpc (Some commitment) ; + let commitment_hash = Octez_smart_rollup.Commitment.hash commitment in + let* () = + Node_context.set_commitment_published_at_level + node_ctxt + commitment_hash + { + first_published_at_level = Raw_level.to_int32 published_at_level; + published_at_level = Some head.Layer1.level; + } + in + let*! () = + Commitment_event.last_published_commitment_updated + commitment_hash + head.Layer1.level + in + return_unit + | ( Sc_rollup_publish {commitment = their_commitment; rollup}, + Sc_rollup_publish_result + {published_at_level; staked_hash = their_commitment_hash; _} ) -> + (* Commitment published by someone else *) + (* We first register the publication information *) + let* known_commitment = + Node_context.commitment_exists node_ctxt their_commitment_hash + in + let* () = + if not known_commitment then return_unit + else + let* republication = + Node_context.commitment_was_published + node_ctxt + ~source:Anyone + their_commitment_hash + in + if republication then return_unit + else + let* () = + Node_context.set_commitment_published_at_level + node_ctxt + their_commitment_hash + { + first_published_at_level = + Raw_level.to_int32 published_at_level; + published_at_level = None; + } + in + return_unit + in + (* An accuser node will publish its commitment if the other one is + refutable. *) + accuser_publish_commitment_when_refutable + node_ctxt + ~other:source + rollup + their_commitment + their_commitment_hash + | ( Sc_rollup_cement _, + Sc_rollup_cement_result {inbox_level; commitment_hash; _} ) -> + (* Cemented commitment ---------------------------------------------- *) + let proto_commitment_hash = commitment_hash in + let inbox_level = Raw_level.to_int32 inbox_level in + let commitment_hash = + Sc_rollup_proto_types.Commitment_hash.to_octez commitment_hash + in + let* inbox_block = + Node_context.get_l2_block_by_level node_ctxt inbox_level + in + let*? () = + (* We stop the node if we disagree with a cemented commitment *) + let our_commitment_hash = inbox_block.header.commitment_hash in + error_unless + (Option.equal + Octez_smart_rollup.Commitment.Hash.( = ) + our_commitment_hash + (Some commitment_hash)) + (Sc_rollup_node_errors.Disagree_with_cemented + {inbox_level; ours = our_commitment_hash; on_l1 = commitment_hash}) + in + let lcc = Reference.get node_ctxt.lcc in + let*! () = + if inbox_level > lcc.level then ( + Reference.set + node_ctxt.lcc + {commitment = proto_commitment_hash; level = inbox_level} ; + Commitment_event.last_cemented_commitment_updated + proto_commitment_hash + inbox_level) + else Lwt.return_unit + in + return_unit + | ( Sc_rollup_refute _, + Sc_rollup_refute_result {game_status = Ended end_status; _} ) + | ( Sc_rollup_timeout _, + Sc_rollup_timeout_result {game_status = Ended end_status; _} ) -> ( + match end_status with + | Loser {loser; reason} when Node_context.is_operator node_ctxt loser -> + let result = + match reason with + | Conflict_resolved -> Sc_rollup_node_errors.Conflict_resolved + | Timeout -> Timeout + in + tzfail (Sc_rollup_node_errors.Lost_game result) + | Loser _ -> + (* Other player lost *) + return_unit + | Draw -> + let stakers = + match operation with + | Sc_rollup_refute {opponent; _} -> [source; opponent] + | Sc_rollup_timeout {stakers = {alice; bob}; _} -> [alice; bob] + | _ -> assert false + in + fail_when + (List.exists (Node_context.is_operator node_ctxt) stakers) + (Sc_rollup_node_errors.Lost_game Draw)) + | Dal_publish_slot_header _, Dal_publish_slot_header_result {slot_header; _} + when Node_context.dal_supported node_ctxt -> + let* () = + Node_context.save_slot_header + node_ctxt + ~published_in_block_hash:head.Layer1.hash + (Sc_rollup_proto_types.Dal.Slot_header.to_octez slot_header) + in + return_unit + | _, _ -> + (* Other manager operations *) + return_unit + +let process_l1_operation (type kind) node_ctxt (head : Layer1.header) ~source + (operation : kind manager_operation) + (result : kind Apply_results.manager_operation_result) = + let open Lwt_result_syntax in + let is_for_my_rollup : type kind. kind manager_operation -> bool = function + | Sc_rollup_add_messages _ -> true + | Sc_rollup_cement {rollup; _} + | Sc_rollup_publish {rollup; _} + | Sc_rollup_refute {rollup; _} + | Sc_rollup_timeout {rollup; _} + | Sc_rollup_execute_outbox_message {rollup; _} + | Sc_rollup_recover_bond {sc_rollup = rollup; staker = _} -> + Sc_rollup.Address.(rollup = node_ctxt.Node_context.rollup_address) + | Dal_publish_slot_header _ -> true + | Reveal _ | Transaction _ | Origination _ | Delegation _ + | Update_consensus_key _ | Register_global_constant _ + | Increase_paid_storage _ | Transfer_ticket _ | Sc_rollup_originate _ + | Zk_rollup_origination _ | Zk_rollup_publish _ | Zk_rollup_update _ -> + false + in + if not (is_for_my_rollup operation) then return_unit + else + (* Only look at operations that are for the node's rollup *) + let*! () = + match Sc_rollup_injector.injector_operation_of_manager operation with + | None -> Lwt.return_unit + | Some op -> + let status, errors = + match result with + | Applied _ -> (`Applied, None) + | Backtracked (_, e) -> + (`Backtracked, Option.map Environment.wrap_tztrace e) + | Failed (_, e) -> (`Failed, Some (Environment.wrap_tztrace e)) + | Skipped _ -> (`Skipped, None) + in + Daemon_event.included_operation ?errors status op + in + match result with + | Applied success_result -> + process_included_l1_operation + node_ctxt + head + ~source + operation + success_result + | _ -> + (* No action for non successful operations *) + return_unit + +let process_l1_block_operations node_ctxt (head : Layer1.header) = + let open Lwt_result_syntax in + let* block = + Layer1_helpers.fetch_tezos_block node_ctxt.Node_context.l1_ctxt head.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 node_ctxt head ~source operation result + in + let apply_internal (type kind) accu ~source:_ + (_operation : kind Apply_internal_results.internal_operation) + (_result : kind Apply_internal_results.internal_operation_result) = + accu + in + let* () = + Layer1_services.process_manager_operations + return_unit + block.operations + {apply; apply_internal} + in + return_unit diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/daemon_helpers.mli b/src/proto_018_Proxford/lib_sc_rollup_node/daemon_helpers.mli new file mode 100644 index 0000000000000000000000000000000000000000..a30441c2b7b76a98965fead547763d7b8ad2859e --- /dev/null +++ b/src/proto_018_Proxford/lib_sc_rollup_node/daemon_helpers.mli @@ -0,0 +1,34 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs, *) +(* Copyright (c) 2023 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. *) +(* *) +(*****************************************************************************) + +(** Ensure that the initial state hash of the PVM as defined by the rollup node + matches the one of the PVM on the L1 node. *) +val check_pvm_initial_state_hash : _ Node_context.t -> unit tzresult Lwt.t + +(** React to L1 operations included in a block of the chain. *) +val process_l1_block_operations : + Node_context.rw -> Layer1.header -> unit tzresult Lwt.t diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/inbox.ml b/src/proto_018_Proxford/lib_sc_rollup_node/inbox.ml index 3262407a4ac4e703ef7ac79f49d2217d3ca5ba67..d19b1a1f9616a54b297ea66d4cc3dfff6c16b1f2 100644 --- a/src/proto_018_Proxford/lib_sc_rollup_node/inbox.ml +++ b/src/proto_018_Proxford/lib_sc_rollup_node/inbox.ml @@ -261,6 +261,14 @@ let payloads_history_of_messages ~is_first_block ~predecessor in payloads_history +let serialize_external_message msg = + Environment.wrap_tzresult + @@ + let open Result_syntax in + let open Sc_rollup.Inbox_message in + let+ msg = serialize @@ External msg in + unsafe_to_string msg + module Internal_for_tests = struct let process_messages = process_messages end diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/inbox.mli b/src/proto_018_Proxford/lib_sc_rollup_node/inbox.mli index 915b87728b2a1f34d500ac0bd304f5355dab13ff..8ef03d9056fcf1defaa2140f3096fc71f76dece3 100644 --- a/src/proto_018_Proxford/lib_sc_rollup_node/inbox.mli +++ b/src/proto_018_Proxford/lib_sc_rollup_node/inbox.mli @@ -90,6 +90,10 @@ val same_as_layer_1 : Octez_smart_rollup.Inbox.t -> unit tzresult Lwt.t +(** Serialize an external messages to the protocol representation. NOTE: this + adds a tag ['\001'] at the beginning. *) +val serialize_external_message : string -> string tzresult + (**/**) module Internal_for_tests : sig diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/rollup_node_plugin.ml b/src/proto_018_Proxford/lib_sc_rollup_node/rollup_node_plugin.ml index af7636f504dcbdb831999a50e1acfffc03eb3630..16ff802accefda3938a8c6fe2b5ec529b3a80337 100644 --- a/src/proto_018_Proxford/lib_sc_rollup_node/rollup_node_plugin.ml +++ b/src/proto_018_Proxford/lib_sc_rollup_node/rollup_node_plugin.ml @@ -32,15 +32,9 @@ module Plugin : Protocol_plugin_sig.S = struct module Interpreter = Interpreter module Publisher = Publisher module Refutation_coordinator = Refutation_coordinator - module Batcher = Batcher + module Batcher_constants = Batcher_constants module Layer1_helpers = Layer1_helpers - - module L1_processing = struct - let check_pvm_initial_state_hash = Daemon.check_initial_state_hash - - let process_l1_block_operations = Daemon.process_l1_block_operations - end - + module L1_processing = Daemon_helpers module Pvm = Pvm_plugin end diff --git a/src/proto_alpha/lib_sc_rollup_node/batcher.ml b/src/proto_alpha/lib_sc_rollup_node/batcher.ml deleted file mode 100644 index 1e9f8967a9cb36c21ee5668657de66110b69a0af..0000000000000000000000000000000000000000 --- a/src/proto_alpha/lib_sc_rollup_node/batcher.ml +++ /dev/null @@ -1,466 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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. *) -(* *) -(*****************************************************************************) - -open Protocol -open Alpha_context -open Batcher_worker_types -module Message_queue = Hash_queue.Make (L2_message.Hash) (L2_message) - -module Batcher_events = Batcher_events.Declare (struct - let worker_name = "batcher" -end) - -module L2_batched_message = struct - type t = {content : string; l1_hash : Injector.Inj_operation.hash} -end - -module Batched_messages = Hash_queue.Make (L2_message.Hash) (L2_batched_message) - -type status = Pending_batch | Batched of Injector.Inj_operation.hash - -(* Same as {!Configuration.batcher} with max_batch_size non optional. *) -type conf = { - simulate : bool; - min_batch_elements : int; - min_batch_size : int; - max_batch_elements : int; - max_batch_size : int; -} - -type state = { - node_ctxt : Node_context.ro; - signer : Signature.public_key_hash; - conf : conf; - messages : Message_queue.t; - batched : Batched_messages.t; - mutable simulation_ctxt : Simulation.t option; -} - -let message_size s = - (* Encoded as length of s on 4 bytes + s *) - 4 + String.length s - -let inject_batch state (l2_messages : L2_message.t list) = - let open Lwt_result_syntax in - let messages = List.map L2_message.content l2_messages in - let operation = L1_operation.Add_messages {messages} in - let+ l1_hash = - Injector.add_pending_operation ~source:state.signer operation - in - List.iter - (fun msg -> - let content = L2_message.content msg in - let hash = L2_message.hash msg in - Batched_messages.replace state.batched hash {content; l1_hash}) - l2_messages - -let inject_batches state = List.iter_es (inject_batch state) - -let get_batches state ~only_full = - let ( current_rev_batch, - current_batch_size, - current_batch_elements, - full_batches ) = - Message_queue.fold - (fun msg_hash - message - ( current_rev_batch, - current_batch_size, - current_batch_elements, - full_batches ) -> - let size = message_size (L2_message.content message) in - let new_batch_size = current_batch_size + size in - let new_batch_elements = current_batch_elements + 1 in - if - new_batch_size <= state.conf.max_batch_size - && new_batch_elements <= state.conf.max_batch_elements - then - (* We can add the message to the current batch because we are still - within the bounds. *) - ( (msg_hash, message) :: current_rev_batch, - new_batch_size, - new_batch_elements, - full_batches ) - else - (* The batch augmented with the message would be too big but it is - below the limit without it. We finalize the current batch and - create a new one for the message. NOTE: Messages in the queue are - always < [state.conf.max_batch_size] because {!on_register} only - accepts those. *) - let batch = List.rev current_rev_batch in - ([(msg_hash, message)], size, 1, batch :: full_batches)) - state.messages - ([], 0, 0, []) - in - let batches = - if - (not only_full) - || current_batch_size >= state.conf.min_batch_size - && current_batch_elements >= state.conf.min_batch_elements - then - (* We have enough to make a batch with the last non-full batch. *) - List.rev current_rev_batch :: full_batches - else full_batches - in - List.fold_left - (fun (batches, to_remove) -> function - | [] -> (batches, to_remove) - | batch -> - let msg_hashes, batch = List.split batch in - let to_remove = List.rev_append msg_hashes to_remove in - (batch :: batches, to_remove)) - ([], []) - batches - -let produce_batches state ~only_full = - let open Lwt_result_syntax in - let batches, to_remove = get_batches state ~only_full in - match batches with - | [] -> return_unit - | _ -> - let* () = inject_batches state batches in - let*! () = - Batcher_events.(emit batched) - (List.length batches, List.length to_remove) - in - List.iter - (fun tr_hash -> Message_queue.remove state.messages tr_hash) - to_remove ; - return_unit - -let simulate node_ctxt simulation_ctxt (messages : L2_message.t list) = - let open Lwt_result_syntax in - let*? ext_messages = - Environment.wrap_tzresult - @@ List.map_e - (fun m -> - let open Result_syntax in - let open Sc_rollup.Inbox_message in - let+ msg = serialize @@ External (L2_message.content m) in - unsafe_to_string msg) - messages - in - let+ simulation_ctxt, _ticks = - Simulation.simulate_messages node_ctxt simulation_ctxt ext_messages - in - simulation_ctxt - -let on_register state (messages : string list) = - let open Lwt_result_syntax in - let max_size_msg = - min - (Protocol.Constants_repr.sc_rollup_message_size_limit - + 4 (* We add 4 because [message_size] adds 4. *)) - state.conf.max_batch_size - in - let*? messages = - List.mapi_e - (fun i message -> - if message_size message > max_size_msg then - error_with "Message %d is too large (max size is %d)" i max_size_msg - else Ok (L2_message.make message)) - messages - in - let* () = - if not state.conf.simulate then return_unit - else - match state.simulation_ctxt with - | None -> failwith "Simulation context of batcher not initialized" - | Some simulation_ctxt -> - let+ simulation_ctxt = - simulate state.node_ctxt simulation_ctxt messages - in - state.simulation_ctxt <- Some simulation_ctxt - in - let*! () = Batcher_events.(emit queue) (List.length messages) in - let hashes = - List.map - (fun message -> - let msg_hash = L2_message.hash message in - Message_queue.replace state.messages msg_hash message ; - msg_hash) - messages - in - let+ () = produce_batches state ~only_full:true in - hashes - -let on_new_head state head = - let open Lwt_result_syntax in - (* Produce batches first *) - let* () = produce_batches state ~only_full:false in - let* simulation_ctxt = - Simulation.start_simulation ~reveal_map:None state.node_ctxt head - in - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4224 - Replay with simulation may be too expensive *) - let+ simulation_ctxt, failing = - if not state.conf.simulate then return (simulation_ctxt, []) - else - (* Re-simulate one by one *) - Message_queue.fold_es - (fun msg_hash msg (simulation_ctxt, failing) -> - let*! result = simulate state.node_ctxt simulation_ctxt [msg] in - match result with - | Ok simulation_ctxt -> return (simulation_ctxt, failing) - | Error _ -> return (simulation_ctxt, msg_hash :: failing)) - state.messages - (simulation_ctxt, []) - in - state.simulation_ctxt <- Some simulation_ctxt ; - (* Forget failing messages *) - List.iter (Message_queue.remove state.messages) failing - -(** Maximum size of an L2 batch in bytes that can fit in an operation of the - protocol. *) -let protocol_max_batch_size = - let open Protocol in - let open Alpha_context in - let empty_message_op : _ Operation.t = - let open Operation in - { - shell = {branch = Block_hash.zero}; - protocol_data = - { - signature = Some Signature.zero; - contents = - Single - (Manager_operation - { - source = Signature.Public_key_hash.zero; - fee = Tez.of_mutez_exn Int64.max_int; - counter = Manager_counter.Internal_for_tests.of_int max_int; - gas_limit = - Gas.Arith.integral_of_int_exn ((max_int - 1) / 1000); - storage_limit = Z.of_int max_int; - operation = Sc_rollup_add_messages {messages = [""]}; - }); - }; - } - in - Protocol.Constants_repr.max_operation_data_length - - Data_encoding.Binary.length - Operation.encoding_with_legacy_attestation_name - (Operation.pack empty_message_op) - -let init_batcher_state node_ctxt ~signer (conf : Configuration.batcher) = - let open Lwt_syntax in - let conf = - { - simulate = conf.simulate; - min_batch_elements = conf.min_batch_elements; - min_batch_size = conf.min_batch_size; - max_batch_elements = conf.max_batch_elements; - max_batch_size = - Option.value conf.max_batch_size ~default:protocol_max_batch_size; - } - in - return - { - node_ctxt; - signer; - conf; - messages = Message_queue.create 100_000 (* ~ 400MB *); - batched = Batched_messages.create 100_000 (* ~ 400MB *); - simulation_ctxt = None; - } - -module Types = struct - type nonrec state = state - - type parameters = { - node_ctxt : Node_context.ro; - signer : Signature.public_key_hash; - conf : Configuration.batcher; - } -end - -module Name = struct - (* We only have a single batcher in the node *) - type t = unit - - let encoding = Data_encoding.unit - - let base = Batcher_events.Worker.section @ ["worker"] - - let pp _ _ = () - - let equal () () = true -end - -module Worker = Worker.MakeSingle (Name) (Request) (Types) - -type worker = Worker.infinite Worker.queue Worker.t - -module Handlers = struct - type self = worker - - let on_request : - type r request_error. - worker -> (r, request_error) Request.t -> (r, request_error) result Lwt.t - = - fun w request -> - let state = Worker.state w in - match request with - | Request.Register messages -> - protect @@ fun () -> on_register state messages - | Request.New_head head -> protect @@ fun () -> on_new_head state head - - type launch_error = error trace - - let on_launch _w () Types.{node_ctxt; signer; conf} = - let open Lwt_result_syntax in - let*! state = init_batcher_state node_ctxt ~signer conf in - return state - - let on_error (type a b) _w st (r : (a, b) Request.t) (errs : b) : - unit tzresult Lwt.t = - let open Lwt_result_syntax in - let request_view = Request.view r in - let emit_and_return_errors errs = - let*! () = - Batcher_events.(emit Worker.request_failed) (request_view, st, errs) - in - return_unit - in - match r with - | Request.Register _ -> emit_and_return_errors errs - | Request.New_head _ -> emit_and_return_errors errs - - let on_completion _w r _ st = - match Request.view r with - | Request.View (Register _ | New_head _) -> - Batcher_events.(emit Worker.request_completed_debug) (Request.view r, st) - - let on_no_request _ = Lwt.return_unit - - let on_close _w = Lwt.return_unit -end - -let table = Worker.create_table Queue - -let worker_promise, worker_waker = Lwt.task () - -let check_batcher_config Configuration.{max_batch_size; _} = - match max_batch_size with - | Some m when m > protocol_max_batch_size -> - error_with - "batcher.max_batch_size must be smaller than %d" - protocol_max_batch_size - | _ -> Ok () - -let start conf ~signer node_ctxt = - let open Lwt_result_syntax in - let*? () = check_batcher_config conf in - let node_ctxt = Node_context.readonly node_ctxt in - let+ worker = - Worker.launch table () {node_ctxt; signer; conf} (module Handlers) - in - Lwt.wakeup worker_waker worker - -let init conf ~signer node_ctxt = - let open Lwt_result_syntax in - match Lwt.state worker_promise with - | Lwt.Return _ -> - (* Worker already started, nothing to do. *) - return_unit - | Lwt.Fail exn -> - (* Worker crashed, not recoverable. *) - fail [Sc_rollup_node_errors.No_batcher; Exn exn] - | Lwt.Sleep -> - (* Never started, start it. *) - start conf ~signer node_ctxt - -(* This is a batcher worker for a single scoru *) -let worker = - lazy - (match Lwt.state worker_promise with - | Lwt.Return worker -> ok worker - | Lwt.Fail _ | Lwt.Sleep -> error Sc_rollup_node_errors.No_batcher) - -let active () = - match Lwt.state worker_promise with - | Lwt.Return _ -> true - | Lwt.Fail _ | Lwt.Sleep -> false - -let find_message hash = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - Message_queue.find_opt state.messages hash - -let get_queue () = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - Message_queue.bindings state.messages - -let handle_request_error rq = - let open Lwt_syntax in - let* rq in - match rq with - | Ok res -> return_ok res - | Error (Worker.Request_error errs) -> Lwt.return_error errs - | Error (Closed None) -> Lwt.return_error [Worker_types.Terminated] - | Error (Closed (Some errs)) -> Lwt.return_error errs - | Error (Any exn) -> Lwt.return_error [Exn exn] - -let register_messages messages = - let open Lwt_result_syntax in - let*? w = Lazy.force worker in - Worker.Queue.push_request_and_wait w (Request.Register messages) - |> handle_request_error - -let new_head b = - let open Lwt_result_syntax in - let w = Lazy.force worker in - match w with - | Error _ -> - (* There is no batcher, nothing to do *) - return_unit - | Ok w -> - Worker.Queue.push_request_and_wait w (Request.New_head b) - |> handle_request_error - -let shutdown () = - let w = Lazy.force worker in - match w with - | Error _ -> - (* There is no batcher, nothing to do *) - Lwt.return_unit - | Ok w -> Worker.shutdown w - -let message_status state msg_hash = - match Message_queue.find_opt state.messages msg_hash with - | Some msg -> Some (Pending_batch, L2_message.content msg) - | None -> ( - match Batched_messages.find_opt state.batched msg_hash with - | Some {content; l1_hash} -> Some (Batched l1_hash, content) - | None -> None) - -let message_status msg_hash = - let open Result_syntax in - let+ w = Lazy.force worker in - let state = Worker.state w in - message_status state msg_hash diff --git a/src/proto_alpha/lib_sc_rollup_node/batcher.mli b/src/proto_alpha/lib_sc_rollup_node/batcher.mli deleted file mode 100644 index f38610bade8e27745ce4bdab2d7f0bfdb09ce572..0000000000000000000000000000000000000000 --- a/src/proto_alpha/lib_sc_rollup_node/batcher.mli +++ /dev/null @@ -1,55 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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. *) -(* *) -(*****************************************************************************) - -include Daemon_components.Batcher_sig - -(** The type for the status of messages in the batcher. *) -type status = - | Pending_batch (** The message is in the queue of the batcher. *) - | Batched of Injector.Inj_operation.hash - (** The message has already been batched and sent to the injector in an L1 - operation whose hash is given. *) - -(** Return [true] if the batcher was started for this node. *) -val active : unit -> bool - -(** Retrieve an L2 message from the queue. *) -val find_message : L2_message.hash -> L2_message.t option tzresult - -(** List all queued messages in the order they appear in the queue, i.e. the - message that were added first to the queue are at the end of list. *) -val get_queue : unit -> (L2_message.hash * L2_message.t) list tzresult - -(** [register_messages messages] registers new L2 [messages] in the queue of the - batcher for future injection on L1. If the batcher was initialized with - [simualte = true], the messages are evaluated the batcher's incremental - simulation context. In this case, when the application fails, the messages - are not queued. *) -val register_messages : string list -> L2_message.hash list tzresult Lwt.t - -(** The status of a message in the batcher. Returns [None] if the message is not - known by the batcher (the batcher only keeps the batched status of the last - 500000 messages). *) -val message_status : L2_message.hash -> (status * string) option tzresult diff --git a/src/proto_alpha/lib_sc_rollup_node/batcher_constants.ml b/src/proto_alpha/lib_sc_rollup_node/batcher_constants.ml new file mode 100644 index 0000000000000000000000000000000000000000..c18ba0722bac0ea1bf201d27a4f927c88b5d0272 --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup_node/batcher_constants.ml @@ -0,0 +1,56 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +let message_size_limit = Protocol.Constants_repr.sc_rollup_message_size_limit + +let protocol_max_batch_size = + let open Protocol in + let open Alpha_context in + let empty_message_op : _ Operation.t = + let open Operation in + { + shell = {branch = Block_hash.zero}; + protocol_data = + { + signature = Some Signature.zero; + contents = + Single + (Manager_operation + { + source = Signature.Public_key_hash.zero; + fee = Tez.of_mutez_exn Int64.max_int; + counter = Manager_counter.Internal_for_tests.of_int max_int; + gas_limit = + Gas.Arith.integral_of_int_exn ((max_int - 1) / 1000); + storage_limit = Z.of_int max_int; + operation = Sc_rollup_add_messages {messages = [""]}; + }); + }; + } + in + Protocol.Constants_repr.max_operation_data_length + - Data_encoding.Binary.length + Operation.encoding_with_legacy_attestation_name + (Operation.pack empty_message_op) diff --git a/src/proto_alpha/lib_sc_rollup_node/batcher_constants.mli b/src/proto_alpha/lib_sc_rollup_node/batcher_constants.mli new file mode 100644 index 0000000000000000000000000000000000000000..dda41ddc6be1ad185be0997f1f289daae059f0c2 --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup_node/batcher_constants.mli @@ -0,0 +1,32 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Maximum size of an L2 message allowed by the prototcol. Is + {!val:Protocol.Constants_repr.sc_rollup_message_size_limit}. *) +val message_size_limit : int + +(** Maximum size in bytes of an batch of L2 messages that can fit in an + operation on L1. It is protocol dependent. *) +val protocol_max_batch_size : int diff --git a/src/proto_alpha/lib_sc_rollup_node/batcher_events.ml b/src/proto_alpha/lib_sc_rollup_node/batcher_events.ml deleted file mode 100644 index abe012a2dd0d266f6970b8f12980736f4f22a7c3..0000000000000000000000000000000000000000 --- a/src/proto_alpha/lib_sc_rollup_node/batcher_events.ml +++ /dev/null @@ -1,91 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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 Declare (WORKER : sig - val worker_name : string -end) = -struct - include Internal_event.Simple - - let section = [Protocol.name; "sc_rollup_node"; WORKER.worker_name] - - let queue = - declare_1 - ~section - ~name:"queue" - ~msg:"adding {nb_messages} to queue" - ~level:Notice - ("nb_messages", Data_encoding.int31) - - let batched = - declare_2 - ~section - ~name:"batched" - ~msg:"batched {nb_messages} messages into {nb_batches} batches" - ~level:Notice - ("nb_batches", Data_encoding.int31) - ("nb_messages", Data_encoding.int31) - - module Worker = struct - open Batcher_worker_types - - let section = section @ ["worker"] - - let request_failed = - declare_3 - ~section - ~name:"request_failed" - ~msg:"request {view} failed ({worker_status}): {errors}" - ~level:Warning - ("view", Request.encoding) - ~pp1:Request.pp - ("worker_status", Worker_types.request_status_encoding) - ~pp2:Worker_types.pp_status - ("errors", Error_monad.trace_encoding) - ~pp3:Error_monad.pp_print_trace - - let request_completed_notice = - declare_2 - ~section - ~name:"request_completed_notice" - ~msg:"{view} {worker_status}" - ~level:Notice - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - - let request_completed_debug = - declare_2 - ~section - ~name:"request_completed_debug" - ~msg:"{view} {worker_status}" - ~level:Debug - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - end -end diff --git a/src/proto_alpha/lib_sc_rollup_node/batcher_worker_types.ml b/src/proto_alpha/lib_sc_rollup_node/batcher_worker_types.ml deleted file mode 100644 index f7ab4535c26b164841fd61fb10f6d4dd0134ad54..0000000000000000000000000000000000000000 --- a/src/proto_alpha/lib_sc_rollup_node/batcher_worker_types.ml +++ /dev/null @@ -1,69 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Nomadic Labs, *) -(* *) -(* 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 Request = struct - type ('a, 'b) t = - | Register : string list -> (L2_message.hash list, error trace) t - | New_head : Layer1.head -> (unit, error trace) t - - type view = View : _ t -> view - - let view req = View req - - let encoding = - let open Data_encoding in - union - [ - case - (Tag 0) - ~title:"Register" - (obj2 - (req "request" (constant "register")) - (req "messages" (list L2_message.content_encoding))) - (function - | View (Register messages) -> Some ((), messages) | _ -> None) - (fun ((), messages) -> View (Register messages)); - case - (Tag 1) - ~title:"New_head" - (obj2 - (req "request" (constant "new_head")) - (req "block" Layer1.head_encoding)) - (function View (New_head b) -> Some ((), b) | _ -> None) - (fun ((), b) -> View (New_head b)); - ] - - let pp ppf (View r) = - match r with - | Register messages -> - Format.fprintf ppf "register %d new L2 message" (List.length messages) - | New_head {Layer1.hash; level} -> - Format.fprintf - ppf - "switching to new L1 head %a at level %ld" - Block_hash.pp - hash - level -end diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon.ml b/src/proto_alpha/lib_sc_rollup_node/daemon.ml index ce639a407cff766237c0dbd58539b48c9a127177..9d5d77f98a8ec4385882eff492ce6fbda9fa20ff 100644 --- a/src/proto_alpha/lib_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/lib_sc_rollup_node/daemon.ml @@ -25,279 +25,7 @@ (* *) (*****************************************************************************) -open Protocol -open Alpha_context -open Apply_results - -(** Returns [Some c] if [their_commitment] is refutable where [c] is our - commitment for the same inbox level. *) -let is_refutable_commitment node_ctxt - (their_commitment : Sc_rollup.Commitment.t) their_commitment_hash = - let open Lwt_result_syntax in - let* l2_block = - Node_context.get_l2_block_by_level - node_ctxt - (Raw_level.to_int32 their_commitment.inbox_level) - in - let* our_commitment_and_hash = - Option.filter_map_es - (fun hash -> - let hash = Sc_rollup_proto_types.Commitment_hash.of_octez hash in - let+ commitment = Node_context.find_commitment node_ctxt hash in - Option.map (fun c -> (c, hash)) commitment) - l2_block.header.commitment_hash - in - match our_commitment_and_hash with - | Some (our_commitment, our_commitment_hash) - when Sc_rollup.Commitment.Hash.( - their_commitment_hash <> our_commitment_hash - && their_commitment.predecessor = our_commitment.predecessor) -> - return our_commitment_and_hash - | _ -> return_none - -(** Publish a commitment when an accuser node sees a refutable commitment. *) -let accuser_publish_commitment_when_refutable node_ctxt ~other rollup - their_commitment their_commitment_hash = - let open Lwt_result_syntax in - when_ (Node_context.is_accuser node_ctxt) @@ fun () -> - (* We are seeing a commitment from someone else. We check if we agree - with it, otherwise the accuser publishes our commitment in order to - play the refutation game. *) - let* refutable = - is_refutable_commitment node_ctxt their_commitment their_commitment_hash - in - match refutable with - | None -> return_unit - | Some (our_commitment, our_commitment_hash) -> - let*! () = - Refutation_game_event.potential_conflict_detected - ~our_commitment_hash - ~their_commitment_hash - ~level:their_commitment.inbox_level - ~other - in - assert (Sc_rollup.Address.(node_ctxt.rollup_address = rollup)) ; - Publisher.publish_single_commitment node_ctxt our_commitment - -(** Process an L1 SCORU operation (for the node's rollup) which is included - for the first time. {b Note}: this function does not process inboxes for - the rollup, which is done instead by {!Inbox.process_head}. *) -let process_included_l1_operation (type kind) (node_ctxt : Node_context.rw) - (head : Layer1.header) ~source (operation : kind manager_operation) - (result : kind successful_manager_operation_result) = - let open Lwt_result_syntax in - match (operation, result) with - | ( Sc_rollup_publish {commitment; _}, - Sc_rollup_publish_result {published_at_level; _} ) - when Node_context.is_operator node_ctxt source -> - (* Published commitment --------------------------------------------- *) - let save_lpc = - match Reference.get node_ctxt.lpc with - | None -> true - | Some lpc -> - Raw_level.to_int32 commitment.inbox_level >= lpc.inbox_level - in - let commitment = Sc_rollup_proto_types.Commitment.to_octez commitment in - if save_lpc then Reference.set node_ctxt.lpc (Some commitment) ; - let commitment_hash = Octez_smart_rollup.Commitment.hash commitment in - let* () = - Node_context.set_commitment_published_at_level - node_ctxt - commitment_hash - { - first_published_at_level = Raw_level.to_int32 published_at_level; - published_at_level = Some head.Layer1.level; - } - in - let*! () = - Commitment_event.last_published_commitment_updated - commitment_hash - head.Layer1.level - in - return_unit - | ( Sc_rollup_publish {commitment = their_commitment; rollup}, - Sc_rollup_publish_result - {published_at_level; staked_hash = their_commitment_hash; _} ) -> - (* Commitment published by someone else *) - (* We first register the publication information *) - let* known_commitment = - Node_context.commitment_exists node_ctxt their_commitment_hash - in - let* () = - if not known_commitment then return_unit - else - let* republication = - Node_context.commitment_was_published - node_ctxt - ~source:Anyone - their_commitment_hash - in - if republication then return_unit - else - let* () = - Node_context.set_commitment_published_at_level - node_ctxt - their_commitment_hash - { - first_published_at_level = - Raw_level.to_int32 published_at_level; - published_at_level = None; - } - in - return_unit - in - (* An accuser node will publish its commitment if the other one is - refutable. *) - accuser_publish_commitment_when_refutable - node_ctxt - ~other:source - rollup - their_commitment - their_commitment_hash - | ( Sc_rollup_cement _, - Sc_rollup_cement_result {inbox_level; commitment_hash; _} ) -> - (* Cemented commitment ---------------------------------------------- *) - let proto_commitment_hash = commitment_hash in - let inbox_level = Raw_level.to_int32 inbox_level in - let commitment_hash = - Sc_rollup_proto_types.Commitment_hash.to_octez commitment_hash - in - let* inbox_block = - Node_context.get_l2_block_by_level node_ctxt inbox_level - in - let*? () = - (* We stop the node if we disagree with a cemented commitment *) - let our_commitment_hash = inbox_block.header.commitment_hash in - error_unless - (Option.equal - Octez_smart_rollup.Commitment.Hash.( = ) - our_commitment_hash - (Some commitment_hash)) - (Sc_rollup_node_errors.Disagree_with_cemented - {inbox_level; ours = our_commitment_hash; on_l1 = commitment_hash}) - in - let lcc = Reference.get node_ctxt.lcc in - let*! () = - if inbox_level > lcc.level then ( - Reference.set - node_ctxt.lcc - {commitment = proto_commitment_hash; level = inbox_level} ; - Commitment_event.last_cemented_commitment_updated - proto_commitment_hash - inbox_level) - else Lwt.return_unit - in - return_unit - | ( Sc_rollup_refute _, - Sc_rollup_refute_result {game_status = Ended end_status; _} ) - | ( Sc_rollup_timeout _, - Sc_rollup_timeout_result {game_status = Ended end_status; _} ) -> ( - match end_status with - | Loser {loser; reason} when Node_context.is_operator node_ctxt loser -> - let result = - match reason with - | Conflict_resolved -> Sc_rollup_node_errors.Conflict_resolved - | Timeout -> Timeout - in - tzfail (Sc_rollup_node_errors.Lost_game result) - | Loser _ -> - (* Other player lost *) - return_unit - | Draw -> - let stakers = - match operation with - | Sc_rollup_refute {opponent; _} -> [source; opponent] - | Sc_rollup_timeout {stakers = {alice; bob}; _} -> [alice; bob] - | _ -> assert false - in - fail_when - (List.exists (Node_context.is_operator node_ctxt) stakers) - (Sc_rollup_node_errors.Lost_game Draw)) - | Dal_publish_slot_header _, Dal_publish_slot_header_result {slot_header; _} - when Node_context.dal_supported node_ctxt -> - let* () = - Node_context.save_slot_header - node_ctxt - ~published_in_block_hash:head.Layer1.hash - (Sc_rollup_proto_types.Dal.Slot_header.to_octez slot_header) - in - return_unit - | _, _ -> - (* Other manager operations *) - return_unit - -let process_l1_operation (type kind) node_ctxt (head : Layer1.header) ~source - (operation : kind manager_operation) - (result : kind Apply_results.manager_operation_result) = - let open Lwt_result_syntax in - let is_for_my_rollup : type kind. kind manager_operation -> bool = function - | Sc_rollup_add_messages _ -> true - | Sc_rollup_cement {rollup; _} - | Sc_rollup_publish {rollup; _} - | Sc_rollup_refute {rollup; _} - | Sc_rollup_timeout {rollup; _} - | Sc_rollup_execute_outbox_message {rollup; _} - | Sc_rollup_recover_bond {sc_rollup = rollup; staker = _} -> - Sc_rollup.Address.(rollup = node_ctxt.Node_context.rollup_address) - | Dal_publish_slot_header _ -> true - | Reveal _ | Transaction _ | Origination _ | Delegation _ - | Update_consensus_key _ | Register_global_constant _ - | Increase_paid_storage _ | Transfer_ticket _ | Sc_rollup_originate _ - | Zk_rollup_origination _ | Zk_rollup_publish _ | Zk_rollup_update _ -> - false - in - if not (is_for_my_rollup operation) then return_unit - else - (* Only look at operations that are for the node's rollup *) - let*! () = - match Sc_rollup_injector.injector_operation_of_manager operation with - | None -> Lwt.return_unit - | Some op -> - let status, errors = - match result with - | Applied _ -> (`Applied, None) - | Backtracked (_, e) -> - (`Backtracked, Option.map Environment.wrap_tztrace e) - | Failed (_, e) -> (`Failed, Some (Environment.wrap_tztrace e)) - | Skipped _ -> (`Skipped, None) - in - Daemon_event.included_operation ?errors status op - in - match result with - | Applied success_result -> - process_included_l1_operation - node_ctxt - head - ~source - operation - success_result - | _ -> - (* No action for non successful operations *) - return_unit - -let process_l1_block_operations node_ctxt (head : Layer1.header) = - let open Lwt_result_syntax in - let* block = - Layer1_helpers.fetch_tezos_block node_ctxt.Node_context.l1_ctxt head.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 node_ctxt head ~source operation result - in - let apply_internal (type kind) accu ~source:_ - (_operation : kind Apply_internal_results.internal_operation) - (_result : kind Apply_internal_results.internal_operation_result) = - accu - in - let* () = - Layer1_services.process_manager_operations - return_unit - block.operations - {apply; apply_internal} - in - return_unit +open Daemon_helpers let before_origination (node_ctxt : _ Node_context.t) (header : Layer1.header) = let origination_level = node_ctxt.genesis_info.level in @@ -563,37 +291,11 @@ let install_finalizer (daemon_components : (module Daemon_components.S)) let* () = Event.shutdown_node exit_status in Tezos_base_unix.Internal_event_unix.close () -let check_initial_state_hash {Node_context.cctxt; rollup_address; kind; _} = - let open Lwt_result_syntax in - let module PVM = (val Pvm.of_kind kind) in - let* l1_reference_initial_state_hash = - RPC.Sc_rollup.initial_pvm_state_hash - (new Protocol_client_context.wrap_full cctxt) - (cctxt#chain, cctxt#block) - rollup_address - in - let*! s = PVM.initial_state ~empty:(PVM.State.empty ()) in - let*! l2_initial_state_hash = PVM.state_hash s in - let l1_reference_initial_state_hash = - Sc_rollup_proto_types.State_hash.to_octez l1_reference_initial_state_hash - in - let l2_initial_state_hash = - Sc_rollup_proto_types.State_hash.to_octez l2_initial_state_hash - in - fail_unless - Octez_smart_rollup.State_hash.( - l1_reference_initial_state_hash = l2_initial_state_hash) - (Sc_rollup_node_errors.Wrong_initial_pvm_state - { - initial_state_hash = l2_initial_state_hash; - expected_state_hash = l1_reference_initial_state_hash; - }) - let run node_ctxt configuration (daemon_components : (module Daemon_components.S)) = let open Lwt_result_syntax in let (module Components) = daemon_components in - let* () = check_initial_state_hash node_ctxt in + let* () = check_pvm_initial_state_hash node_ctxt in let* rpc_server = RPC_server.start node_ctxt configuration in let (_ : Lwt_exit.clean_up_callback_id) = install_finalizer daemon_components node_ctxt rpc_server @@ -776,7 +478,12 @@ module Internal_for_tests = struct end module Rollup_node_daemon_components : Daemon_components.S = struct - module Batcher = Batcher + module Batcher = struct + include Batcher + + let init c = init (module Rollup_node_plugin.Plugin) c + end + module RPC_server = RPC_server end diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon_helpers.ml b/src/proto_alpha/lib_sc_rollup_node/daemon_helpers.ml new file mode 100644 index 0000000000000000000000000000000000000000..57315ced452c02e0f548959d783bc549d5c003a2 --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup_node/daemon_helpers.ml @@ -0,0 +1,326 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs, *) +(* Copyright (c) 2023 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context +open Apply_results + +let check_pvm_initial_state_hash {Node_context.cctxt; rollup_address; kind; _} = + let open Lwt_result_syntax in + let module PVM = (val Pvm.of_kind kind) in + let* l1_reference_initial_state_hash = + RPC.Sc_rollup.initial_pvm_state_hash + (new Protocol_client_context.wrap_full cctxt) + (cctxt#chain, cctxt#block) + rollup_address + in + let*! s = PVM.initial_state ~empty:(PVM.State.empty ()) in + let*! l2_initial_state_hash = PVM.state_hash s in + let l1_reference_initial_state_hash = + Sc_rollup_proto_types.State_hash.to_octez l1_reference_initial_state_hash + in + let l2_initial_state_hash = + Sc_rollup_proto_types.State_hash.to_octez l2_initial_state_hash + in + fail_unless + Octez_smart_rollup.State_hash.( + l1_reference_initial_state_hash = l2_initial_state_hash) + (Sc_rollup_node_errors.Wrong_initial_pvm_state + { + initial_state_hash = l2_initial_state_hash; + expected_state_hash = l1_reference_initial_state_hash; + }) + +(** Returns [Some c] if [their_commitment] is refutable where [c] is our + commitment for the same inbox level. *) +let is_refutable_commitment node_ctxt + (their_commitment : Sc_rollup.Commitment.t) their_commitment_hash = + let open Lwt_result_syntax in + let* l2_block = + Node_context.get_l2_block_by_level + node_ctxt + (Raw_level.to_int32 their_commitment.inbox_level) + in + let* our_commitment_and_hash = + Option.filter_map_es + (fun hash -> + let hash = Sc_rollup_proto_types.Commitment_hash.of_octez hash in + let+ commitment = Node_context.find_commitment node_ctxt hash in + Option.map (fun c -> (c, hash)) commitment) + l2_block.header.commitment_hash + in + match our_commitment_and_hash with + | Some (our_commitment, our_commitment_hash) + when Sc_rollup.Commitment.Hash.( + their_commitment_hash <> our_commitment_hash + && their_commitment.predecessor = our_commitment.predecessor) -> + return our_commitment_and_hash + | _ -> return_none + +(** Publish a commitment when an accuser node sees a refutable commitment. *) +let accuser_publish_commitment_when_refutable node_ctxt ~other rollup + their_commitment their_commitment_hash = + let open Lwt_result_syntax in + when_ (Node_context.is_accuser node_ctxt) @@ fun () -> + (* We are seeing a commitment from someone else. We check if we agree + with it, otherwise the accuser publishes our commitment in order to + play the refutation game. *) + let* refutable = + is_refutable_commitment node_ctxt their_commitment their_commitment_hash + in + match refutable with + | None -> return_unit + | Some (our_commitment, our_commitment_hash) -> + let*! () = + Refutation_game_event.potential_conflict_detected + ~our_commitment_hash + ~their_commitment_hash + ~level:their_commitment.inbox_level + ~other + in + assert (Sc_rollup.Address.(node_ctxt.rollup_address = rollup)) ; + Publisher.publish_single_commitment node_ctxt our_commitment + +(** Process an L1 SCORU operation (for the node's rollup) which is included + for the first time. {b Note}: this function does not process inboxes for + the rollup, which is done instead by {!Inbox.process_head}. *) +let process_included_l1_operation (type kind) (node_ctxt : Node_context.rw) + (head : Layer1.header) ~source (operation : kind manager_operation) + (result : kind successful_manager_operation_result) = + let open Lwt_result_syntax in + match (operation, result) with + | ( Sc_rollup_publish {commitment; _}, + Sc_rollup_publish_result {published_at_level; _} ) + when Node_context.is_operator node_ctxt source -> + (* Published commitment --------------------------------------------- *) + let save_lpc = + match Reference.get node_ctxt.lpc with + | None -> true + | Some lpc -> + Raw_level.to_int32 commitment.inbox_level >= lpc.inbox_level + in + let commitment = Sc_rollup_proto_types.Commitment.to_octez commitment in + if save_lpc then Reference.set node_ctxt.lpc (Some commitment) ; + let commitment_hash = Octez_smart_rollup.Commitment.hash commitment in + let* () = + Node_context.set_commitment_published_at_level + node_ctxt + commitment_hash + { + first_published_at_level = Raw_level.to_int32 published_at_level; + published_at_level = Some head.Layer1.level; + } + in + let*! () = + Commitment_event.last_published_commitment_updated + commitment_hash + head.Layer1.level + in + return_unit + | ( Sc_rollup_publish {commitment = their_commitment; rollup}, + Sc_rollup_publish_result + {published_at_level; staked_hash = their_commitment_hash; _} ) -> + (* Commitment published by someone else *) + (* We first register the publication information *) + let* known_commitment = + Node_context.commitment_exists node_ctxt their_commitment_hash + in + let* () = + if not known_commitment then return_unit + else + let* republication = + Node_context.commitment_was_published + node_ctxt + ~source:Anyone + their_commitment_hash + in + if republication then return_unit + else + let* () = + Node_context.set_commitment_published_at_level + node_ctxt + their_commitment_hash + { + first_published_at_level = + Raw_level.to_int32 published_at_level; + published_at_level = None; + } + in + return_unit + in + (* An accuser node will publish its commitment if the other one is + refutable. *) + accuser_publish_commitment_when_refutable + node_ctxt + ~other:source + rollup + their_commitment + their_commitment_hash + | ( Sc_rollup_cement _, + Sc_rollup_cement_result {inbox_level; commitment_hash; _} ) -> + (* Cemented commitment ---------------------------------------------- *) + let proto_commitment_hash = commitment_hash in + let inbox_level = Raw_level.to_int32 inbox_level in + let commitment_hash = + Sc_rollup_proto_types.Commitment_hash.to_octez commitment_hash + in + let* inbox_block = + Node_context.get_l2_block_by_level node_ctxt inbox_level + in + let*? () = + (* We stop the node if we disagree with a cemented commitment *) + let our_commitment_hash = inbox_block.header.commitment_hash in + error_unless + (Option.equal + Octez_smart_rollup.Commitment.Hash.( = ) + our_commitment_hash + (Some commitment_hash)) + (Sc_rollup_node_errors.Disagree_with_cemented + {inbox_level; ours = our_commitment_hash; on_l1 = commitment_hash}) + in + let lcc = Reference.get node_ctxt.lcc in + let*! () = + if inbox_level > lcc.level then ( + Reference.set + node_ctxt.lcc + {commitment = proto_commitment_hash; level = inbox_level} ; + Commitment_event.last_cemented_commitment_updated + proto_commitment_hash + inbox_level) + else Lwt.return_unit + in + return_unit + | ( Sc_rollup_refute _, + Sc_rollup_refute_result {game_status = Ended end_status; _} ) + | ( Sc_rollup_timeout _, + Sc_rollup_timeout_result {game_status = Ended end_status; _} ) -> ( + match end_status with + | Loser {loser; reason} when Node_context.is_operator node_ctxt loser -> + let result = + match reason with + | Conflict_resolved -> Sc_rollup_node_errors.Conflict_resolved + | Timeout -> Timeout + in + tzfail (Sc_rollup_node_errors.Lost_game result) + | Loser _ -> + (* Other player lost *) + return_unit + | Draw -> + let stakers = + match operation with + | Sc_rollup_refute {opponent; _} -> [source; opponent] + | Sc_rollup_timeout {stakers = {alice; bob}; _} -> [alice; bob] + | _ -> assert false + in + fail_when + (List.exists (Node_context.is_operator node_ctxt) stakers) + (Sc_rollup_node_errors.Lost_game Draw)) + | Dal_publish_slot_header _, Dal_publish_slot_header_result {slot_header; _} + when Node_context.dal_supported node_ctxt -> + let* () = + Node_context.save_slot_header + node_ctxt + ~published_in_block_hash:head.Layer1.hash + (Sc_rollup_proto_types.Dal.Slot_header.to_octez slot_header) + in + return_unit + | _, _ -> + (* Other manager operations *) + return_unit + +let process_l1_operation (type kind) node_ctxt (head : Layer1.header) ~source + (operation : kind manager_operation) + (result : kind Apply_results.manager_operation_result) = + let open Lwt_result_syntax in + let is_for_my_rollup : type kind. kind manager_operation -> bool = function + | Sc_rollup_add_messages _ -> true + | Sc_rollup_cement {rollup; _} + | Sc_rollup_publish {rollup; _} + | Sc_rollup_refute {rollup; _} + | Sc_rollup_timeout {rollup; _} + | Sc_rollup_execute_outbox_message {rollup; _} + | Sc_rollup_recover_bond {sc_rollup = rollup; staker = _} -> + Sc_rollup.Address.(rollup = node_ctxt.Node_context.rollup_address) + | Dal_publish_slot_header _ -> true + | Reveal _ | Transaction _ | Origination _ | Delegation _ + | Update_consensus_key _ | Register_global_constant _ + | Increase_paid_storage _ | Transfer_ticket _ | Sc_rollup_originate _ + | Zk_rollup_origination _ | Zk_rollup_publish _ | Zk_rollup_update _ -> + false + in + if not (is_for_my_rollup operation) then return_unit + else + (* Only look at operations that are for the node's rollup *) + let*! () = + match Sc_rollup_injector.injector_operation_of_manager operation with + | None -> Lwt.return_unit + | Some op -> + let status, errors = + match result with + | Applied _ -> (`Applied, None) + | Backtracked (_, e) -> + (`Backtracked, Option.map Environment.wrap_tztrace e) + | Failed (_, e) -> (`Failed, Some (Environment.wrap_tztrace e)) + | Skipped _ -> (`Skipped, None) + in + Daemon_event.included_operation ?errors status op + in + match result with + | Applied success_result -> + process_included_l1_operation + node_ctxt + head + ~source + operation + success_result + | _ -> + (* No action for non successful operations *) + return_unit + +let process_l1_block_operations node_ctxt (head : Layer1.header) = + let open Lwt_result_syntax in + let* block = + Layer1_helpers.fetch_tezos_block node_ctxt.Node_context.l1_ctxt head.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 node_ctxt head ~source operation result + in + let apply_internal (type kind) accu ~source:_ + (_operation : kind Apply_internal_results.internal_operation) + (_result : kind Apply_internal_results.internal_operation_result) = + accu + in + let* () = + Layer1_services.process_manager_operations + return_unit + block.operations + {apply; apply_internal} + in + return_unit diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon_helpers.mli b/src/proto_alpha/lib_sc_rollup_node/daemon_helpers.mli new file mode 100644 index 0000000000000000000000000000000000000000..a30441c2b7b76a98965fead547763d7b8ad2859e --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup_node/daemon_helpers.mli @@ -0,0 +1,34 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs, *) +(* Copyright (c) 2023 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. *) +(* *) +(*****************************************************************************) + +(** Ensure that the initial state hash of the PVM as defined by the rollup node + matches the one of the PVM on the L1 node. *) +val check_pvm_initial_state_hash : _ Node_context.t -> unit tzresult Lwt.t + +(** React to L1 operations included in a block of the chain. *) +val process_l1_block_operations : + Node_context.rw -> Layer1.header -> unit tzresult Lwt.t diff --git a/src/proto_alpha/lib_sc_rollup_node/inbox.ml b/src/proto_alpha/lib_sc_rollup_node/inbox.ml index 3262407a4ac4e703ef7ac79f49d2217d3ca5ba67..d19b1a1f9616a54b297ea66d4cc3dfff6c16b1f2 100644 --- a/src/proto_alpha/lib_sc_rollup_node/inbox.ml +++ b/src/proto_alpha/lib_sc_rollup_node/inbox.ml @@ -261,6 +261,14 @@ let payloads_history_of_messages ~is_first_block ~predecessor in payloads_history +let serialize_external_message msg = + Environment.wrap_tzresult + @@ + let open Result_syntax in + let open Sc_rollup.Inbox_message in + let+ msg = serialize @@ External msg in + unsafe_to_string msg + module Internal_for_tests = struct let process_messages = process_messages end diff --git a/src/proto_alpha/lib_sc_rollup_node/inbox.mli b/src/proto_alpha/lib_sc_rollup_node/inbox.mli index 915b87728b2a1f34d500ac0bd304f5355dab13ff..8ef03d9056fcf1defaa2140f3096fc71f76dece3 100644 --- a/src/proto_alpha/lib_sc_rollup_node/inbox.mli +++ b/src/proto_alpha/lib_sc_rollup_node/inbox.mli @@ -90,6 +90,10 @@ val same_as_layer_1 : Octez_smart_rollup.Inbox.t -> unit tzresult Lwt.t +(** Serialize an external messages to the protocol representation. NOTE: this + adds a tag ['\001'] at the beginning. *) +val serialize_external_message : string -> string tzresult + (**/**) module Internal_for_tests : sig diff --git a/src/proto_alpha/lib_sc_rollup_node/rollup_node_plugin.ml b/src/proto_alpha/lib_sc_rollup_node/rollup_node_plugin.ml index af7636f504dcbdb831999a50e1acfffc03eb3630..16ff802accefda3938a8c6fe2b5ec529b3a80337 100644 --- a/src/proto_alpha/lib_sc_rollup_node/rollup_node_plugin.ml +++ b/src/proto_alpha/lib_sc_rollup_node/rollup_node_plugin.ml @@ -32,15 +32,9 @@ module Plugin : Protocol_plugin_sig.S = struct module Interpreter = Interpreter module Publisher = Publisher module Refutation_coordinator = Refutation_coordinator - module Batcher = Batcher + module Batcher_constants = Batcher_constants module Layer1_helpers = Layer1_helpers - - module L1_processing = struct - let check_pvm_initial_state_hash = Daemon.check_initial_state_hash - - let process_l1_block_operations = Daemon.process_l1_block_operations - end - + module L1_processing = Daemon_helpers module Pvm = Pvm_plugin end