diff --git a/src/lib_crawler/layer_1.ml b/src/lib_crawler/layer_1.ml index fd424a189c2d54fab5223e957c6b50c67a036a52..524c59462d8441010a494c1c4ae545de19e7f53a 100644 --- a/src/lib_crawler/layer_1.ml +++ b/src/lib_crawler/layer_1.ml @@ -58,6 +58,7 @@ let () = type t = { name : string; + protocols : Protocol_hash.t list option; reconnection_delay : float; heads : (Block_hash.t * Block_header.t) Lwt_stream.t; cctxt : Client_context.full; @@ -65,7 +66,7 @@ type t = { mutable running : bool; } -let rec connect ~name ?(count = 0) ~delay cctxt = +let rec connect ~name ?(count = 0) ~delay ~protocols cctxt = let open Lwt_syntax in let* () = if count = 0 then return_unit @@ -83,7 +84,9 @@ let rec connect ~name ?(count = 0) ~delay cctxt = let* () = Layer1_event.wait_reconnect ~name delay in Lwt_unix.sleep delay in - let* res = Tezos_shell_services.Monitor_services.heads cctxt cctxt#chain in + let* res = + Tezos_shell_services.Monitor_services.heads ?protocols cctxt cctxt#chain + in match res with | Ok (heads, stopper) -> let heads = @@ -94,31 +97,35 @@ let rec connect ~name ?(count = 0) ~delay cctxt = (hash, header)) heads in - return_ok (heads, stopper) + return (heads, stopper) | Error e -> let* () = Layer1_event.cannot_connect ~name ~count e in - connect ~name ~delay ~count:(count + 1) cctxt + connect ~name ~delay ~protocols ~count:(count + 1) cctxt -let start ~name ~reconnection_delay (cctxt : #Client_context.full) = - let open Lwt_result_syntax in - let*! () = Layer1_event.starting ~name in - let+ heads, stopper = connect ~name ~delay:reconnection_delay cctxt in +let start ~name ~reconnection_delay ?protocols (cctxt : #Client_context.full) = + let open Lwt_syntax in + let* () = Layer1_event.starting ~name in + let+ heads, stopper = + connect ~name ~delay:reconnection_delay ~protocols cctxt + in { name; cctxt = (cctxt :> Client_context.full); heads; stopper; reconnection_delay; + protocols; running = true; } let reconnect l1_ctxt = - let open Lwt_result_syntax in + let open Lwt_syntax in let* heads, stopper = connect ~name:l1_ctxt.name ~count:1 ~delay:l1_ctxt.reconnection_delay + ~protocols:l1_ctxt.protocols l1_ctxt.cctxt in return {l1_ctxt with heads; stopper} @@ -165,13 +172,25 @@ let iter_heads l1_ctxt f = in when_ l1_ctxt.running @@ fun () -> let*! () = Layer1_event.connection_lost ~name:l1_ctxt.name in - let* l1_ctxt = reconnect l1_ctxt in + let*! l1_ctxt = reconnect l1_ctxt in loop l1_ctxt in Lwt.catch (fun () -> Lwt.no_cancel @@ loop l1_ctxt) (function Iter_error e -> Lwt.return_error e | exn -> fail (Exn exn)) +let wait_first l1_ctxt = + let rec loop l1_ctxt = + let open Lwt_syntax in + let* head = Lwt_stream.peek l1_ctxt.heads in + match head with + | Some head -> return head + | None -> + let* l1_ctxt = reconnect l1_ctxt in + loop l1_ctxt + in + Lwt.no_cancel @@ loop l1_ctxt + (** [predecessors_of_blocks hashes] given a list of successive block hashes, from newest to oldest, returns an associative list that associates a hash to its predecessor in this list. *) @@ -325,6 +344,7 @@ module Internal_for_tests = struct heads; cctxt = (cctxt :> Client_context.full); stopper = Fun.id; + protocols = None; running = false; } end diff --git a/src/lib_crawler/layer_1.mli b/src/lib_crawler/layer_1.mli index bb50c95d567c16bd07d4560ed5fcbf4f07a8593c..f5573d58d6e3990b12ae9d2a970f8bd72a0b4723 100644 --- a/src/lib_crawler/layer_1.mli +++ b/src/lib_crawler/layer_1.mli @@ -34,16 +34,18 @@ type t (** {2 Monitoring the Layer 1 chain} *) -(** [start ~name ~reconnection_delay cctxt] connects to a Tezos node and starts - monitoring new heads. One can iterate on the heads by calling {!iter_heads} - on its result. [reconnection_delay] gives an initial delay for the - reconnection which is used in an exponential backoff. The [name] is used to - differentiate events. *) +(** [start ~name ~reconnection_delay ?protocols cctxt] connects to a Tezos node + and starts monitoring new heads. One can iterate on the heads by calling + {!iter_heads} on its result. [reconnection_delay] gives an initial delay for + the reconnection which is used in an exponential backoff. The [name] is used + to differentiate events. If [protocols] is provided, only heads of these + protocols will be monitored. *) val start : name:string -> reconnection_delay:float -> + ?protocols:Protocol_hash.t list -> #Client_context.full -> - t tzresult Lwt.t + t Lwt.t (** [shutdown t] properly shuts the layer 1 down. *) val shutdown : t -> unit Lwt.t @@ -57,6 +59,10 @@ val iter_heads : (Block_hash.t * Block_header.t -> unit tzresult Lwt.t) -> unit tzresult Lwt.t +(** [wait_first t] waits for the first head to appear in the stream and + returns it. *) +val wait_first : t -> (Block_hash.t * Block_header.t) Lwt.t + (** {2 Helper functions for the Layer 1 chain} *) (** [get_predecessor_opt ?max_read state head] returns the predecessor of block diff --git a/src/lib_injector/injector_functor.ml b/src/lib_injector/injector_functor.ml index 16961853ff51568110d8c56d829523416125aeca..cc5fb99770baca0fdd28983bc97cd6b072ccbcc8 100644 --- a/src/lib_injector/injector_functor.ml +++ b/src/lib_injector/injector_functor.ml @@ -1440,7 +1440,7 @@ module Make (Parameters : PARAMETERS) = struct Signature.Public_key_hash.Map.empty signers in - let* l1_ctxt = Layer_1.start ~name:"injector" ~reconnection_delay cctxt in + let*! l1_ctxt = Layer_1.start ~name:"injector" ~reconnection_delay cctxt in let* head_protocols = protocols_of_head cctxt in let* () = Signature.Public_key_hash.Map.iter_es 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 b321330df6252c2ebfd8f92238b866f4f23886c4..625f5f05f32bbec455497f17c04b676824d79701 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml @@ -292,6 +292,44 @@ let previous_context (node_ctxt : _ Node_context.t) return (Context.empty node_ctxt.context) else Node_context.checkout_context node_ctxt predecessor.Layer1.hash +let classify_head (node_ctxt : _ Node_context.t) + ?(predecessor : Layer1.header option) (head : Layer1.header) = + let open Lwt_result_syntax in + if head.header.proto_level = node_ctxt.proto_level then + (* Same protocol as supported one, ok *) + return `Supported_proto + else if head.header.proto_level = node_ctxt.proto_level + 1 then + let* predecessor = + match predecessor with + | Some p -> return p + | None -> Node_context.get_predecessor_header node_ctxt head + in + if predecessor.header.proto_level = node_ctxt.proto_level then + (* Migration block from supported protocol to the next one, ok *) + return `Supported_migration + else return `Unsupported_proto + else return `Unsupported_proto + +let exit_on_other_proto node_ctxt ?predecessor head = + let open Lwt_result_syntax in + let* proto_class = classify_head node_ctxt ?predecessor head in + match proto_class with + | `Supported_proto | `Supported_migration -> return_unit + | `Unsupported_proto -> + let*! () = Event.detected_protocol_migration () in + let*! _ = Lwt_exit.exit_and_wait 0 in + return_unit + +let exit_after_proto_migration node_ctxt ?predecessor head = + let open Lwt_result_syntax in + let* proto_class = classify_head node_ctxt ?predecessor head in + match proto_class with + | `Supported_proto -> return_unit + | `Supported_migration | `Unsupported_proto -> + let*! () = Event.detected_protocol_migration () in + let*! _ = Lwt_exit.exit_and_wait 0 in + return_unit + let rec process_head (node_ctxt : _ Node_context.t) (head : Layer1.header) = let open Lwt_result_syntax in let* already_processed = Node_context.is_processed node_ctxt head.hash in @@ -304,6 +342,7 @@ let rec process_head (node_ctxt : _ Node_context.t) (head : Layer1.header) = exist in the chain. *) return_unit | Some predecessor -> + let* () = exit_on_other_proto node_ctxt ~predecessor head in let* () = process_head node_ctxt predecessor in let* ctxt = previous_context node_ctxt ~predecessor in let* () = @@ -435,6 +474,7 @@ let on_layer_1_head node_ctxt (head : Layer1.header) = let* () = Batcher.batch () in let* () = Batcher.new_head stripped_head in let*! () = Injector.inject ~header:head.header () in + let* () = exit_after_proto_migration node_ctxt head in return_unit let daemonize (node_ctxt : _ Node_context.t) = @@ -451,6 +491,7 @@ let degraded_refutation_mode (node_ctxt : _ Node_context.t) = Layer1.iter_heads node_ctxt.l1_ctxt @@ fun head -> let* () = Refutation_coordinator.process (Layer1.head_of_header head) in let*! () = Injector.inject () in + let* () = exit_on_other_proto node_ctxt head in return_unit let install_finalizer node_ctxt rpc_server = @@ -645,12 +686,25 @@ let run ~data_dir ?log_kernel_debug_file (configuration : Configuration.t) ()) configuration.sc_rollup_node_operators in + let*! () = Event.waiting_first_block () in + let*! l1_ctxt = + Layer1.start + ~name:"sc_rollup_node" + ~reconnection_delay:configuration.reconnection_delay + ~protocols:[Protocol.hash] + cctxt + in + let*! head, {shell = {predecessor; _}; _} = Layer1.wait_first l1_ctxt in + let* predecessor = Layer1.fetch_tezos_shell_header cctxt predecessor in + let*! () = Event.received_first_block head in let* node_ctxt = Node_context.init cctxt ~data_dir ?log_kernel_debug_file Read_write + l1_ctxt + ~proto_level:predecessor.proto_level configuration in run node_ctxt configuration diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/event.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/event.ml index 79b27226eef8bf0561b339fc1421099bf9873487..c35631692bfb4f5e25e70842121564d11c104555 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/event.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/event.ml @@ -125,6 +125,38 @@ module Simple = struct for the rollup node." () + let waiting_first_block = + declare_0 + ~section + ~name:"waiting_first_block" + ~level:Notice + ~msg: + (Format.asprintf + "Waiting for first block of protocol %a to appear." + Protocol_hash.pp + Protocol.hash) + () + + let received_first_block = + declare_1 + ~section + ~name:"received_first_block" + ~level:Notice + ~msg: + (Format.asprintf + "First block of protocol %a received: {block}." + Protocol_hash.pp + Protocol.hash) + ("block", Block_hash.encoding) + + let detected_protocol_migration = + declare_0 + ~section + ~name:"detected_protocol_migration" + ~level:Notice + ~msg:"Detected protocol migration, the rollup node will now stop." + () + let acquiring_lock = declare_0 ~section @@ -166,4 +198,11 @@ let kernel_debug_dont_wait msg = let warn_dal_enabled_no_node () = Simple.(emit warn_dal_enabled_no_node) () +let waiting_first_block () = Simple.(emit waiting_first_block) () + +let received_first_block b = Simple.(emit received_first_block) b + +let detected_protocol_migration () = + Simple.(emit detected_protocol_migration) () + let acquiring_lock () = Simple.(emit acquiring_lock) () diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/event.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/event.mli index 188c37b99008a79c9a7fc8fc561b0e57d9223478..cb90a98c4225db9d3cf45f4d1d6f9da755962118 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/event.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/event.mli @@ -76,6 +76,15 @@ val kernel_debug_dont_wait : string -> unit protocol but the rollup node has no DAL node. *) val warn_dal_enabled_no_node : unit -> unit Lwt.t +(** Emit event that the node is waiting for the first block of its protocol. *) +val waiting_first_block : unit -> unit Lwt.t + +(** Emit event that the node received the first block of its protocol. *) +val received_first_block : Block_hash.t -> unit Lwt.t + +(** Emit event that the node will shutdown because of protocol migration. *) +val detected_protocol_migration : unit -> unit Lwt.t + (** [acquiring_lock ()] emits an event to indicate that the node is attempting to acquire a lock on the data directory. *) val acquiring_lock : unit -> unit Lwt.t diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.ml index 5bfd2bcaddb4401fbccfa84320910f7e4d7a8572..ba977cc57828e0aa96c922467a7cfe9321f72303 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.ml @@ -48,6 +48,7 @@ type 'a t = { pvm : (module Pvm.S); fee_parameters : Configuration.fee_parameters; protocol_constants : Constants.t; + proto_level : int; loser_mode : Loser_mode.t; lockfile : Lwt_unix.file_descr; store : 'a store; @@ -277,7 +278,7 @@ let check_config config = () let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file - mode + mode l1_ctxt ~proto_level Configuration.( { sc_rollup_address = rollup_address; @@ -287,7 +288,6 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file loser_mode; l2_blocks_cache_size; dal_node_endpoint; - reconnection_delay; _; } as configuration) = let open Lwt_result_syntax in @@ -315,9 +315,6 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file Context.load mode (Configuration.default_context_dir data_dir) in let* () = Context.Rollup.check_or_set_address mode context rollup_address in - let* l1_ctxt = - Layer1.start ~name:"sc_rollup_node" ~reconnection_delay cctxt - in let publisher = Configuration.Operator_purpose_map.find Publish operators in let* protocol_constants = retrieve_constants cctxt and* lcc = get_last_cemented_commitment cctxt rollup_address @@ -368,6 +365,7 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file block_finality_time = 2; fee_parameters; protocol_constants; + proto_level; loser_mode; lockfile; store; @@ -1007,6 +1005,7 @@ module Internal_for_tests = struct block_finality_time = 2; fee_parameters = Configuration.default_fee_parameters; protocol_constants; + proto_level = 0; loser_mode = Loser_mode.no_failures; lockfile; store; diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.mli index ccb0969dfee60e7f466e507d453c4de078da32a3..f2fdb44a470f24a30b43fb35bd447a1925cb4892 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.mli @@ -63,6 +63,9 @@ type 'a t = { (** Fee parameters to use when injecting operations in layer 1. *) protocol_constants : Constants.t; (** Protocol constants retrieved from the Tezos node. *) + proto_level : int; + (** Protocol supported by this rollup node (represented as a protocol + level). *) loser_mode : Loser_mode.t; (** If different from [Loser_mode.no_failures], the rollup node issues wrong commitments (for tests). *) @@ -119,15 +122,17 @@ val get_fee_parameter : protocol. *) val protocol_max_batch_size : int -(** [init cctxt ~data_dir mode configuration] initializes the rollup - representation. The rollup origination level and kind are fetched via an RPC - call to the layer1 node that [cctxt] uses for RPC requests. +(** [init cctxt ~data_dir mode l1_ctxt ~proto_level configuration] initializes + the rollup representation. The rollup origination level and kind are fetched + via an RPC call to the layer1 node that [cctxt] uses for RPC requests. *) val init : Protocol_client_context.full -> data_dir:string -> ?log_kernel_debug_file:string -> 'a Store_sigs.mode -> + Layer1.t -> + proto_level:int -> Configuration.t -> 'a t tzresult Lwt.t 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 7a26c556ec3c9ff9a4570c83f8cc3dd25d786b4a..1034876b5e31256bac0264a6c6afee04161d1f33 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml @@ -290,6 +290,44 @@ let previous_context (node_ctxt : _ Node_context.t) return (Context.empty node_ctxt.context) else Node_context.checkout_context node_ctxt predecessor.Layer1.hash +let classify_head (node_ctxt : _ Node_context.t) + ?(predecessor : Layer1.header option) (head : Layer1.header) = + let open Lwt_result_syntax in + if head.header.proto_level = node_ctxt.proto_level then + (* Same protocol as supported one, ok *) + return `Supported_proto + else if head.header.proto_level = node_ctxt.proto_level + 1 then + let* predecessor = + match predecessor with + | Some p -> return p + | None -> Node_context.get_predecessor_header node_ctxt head + in + if predecessor.header.proto_level = node_ctxt.proto_level then + (* Migration block from supported protocol to the next one, ok *) + return `Supported_migration + else return `Unsupported_proto + else return `Unsupported_proto + +let exit_on_other_proto node_ctxt ?predecessor head = + let open Lwt_result_syntax in + let* proto_class = classify_head node_ctxt ?predecessor head in + match proto_class with + | `Supported_proto | `Supported_migration -> return_unit + | `Unsupported_proto -> + let*! () = Event.detected_protocol_migration () in + let*! _ = Lwt_exit.exit_and_wait 0 in + return_unit + +let exit_after_proto_migration node_ctxt ?predecessor head = + let open Lwt_result_syntax in + let* proto_class = classify_head node_ctxt ?predecessor head in + match proto_class with + | `Supported_proto -> return_unit + | `Supported_migration | `Unsupported_proto -> + let*! () = Event.detected_protocol_migration () in + let*! _ = Lwt_exit.exit_and_wait 0 in + return_unit + let rec process_head (node_ctxt : _ Node_context.t) (head : Layer1.header) = let open Lwt_result_syntax in let* already_processed = Node_context.is_processed node_ctxt head.hash in @@ -302,6 +340,7 @@ let rec process_head (node_ctxt : _ Node_context.t) (head : Layer1.header) = exist in the chain. *) return_unit | Some predecessor -> + let* () = exit_on_other_proto node_ctxt ~predecessor head in let* () = process_head node_ctxt predecessor in let* ctxt = previous_context node_ctxt ~predecessor in let* () = @@ -433,6 +472,7 @@ let on_layer_1_head node_ctxt (head : Layer1.header) = let* () = Batcher.batch () in let* () = Batcher.new_head stripped_head in let*! () = Injector.inject ~header:head.header () in + let* () = exit_after_proto_migration node_ctxt head in return_unit let daemonize (node_ctxt : _ Node_context.t) = @@ -449,6 +489,7 @@ let degraded_refutation_mode (node_ctxt : _ Node_context.t) = Layer1.iter_heads node_ctxt.l1_ctxt @@ fun head -> let* () = Refutation_coordinator.process (Layer1.head_of_header head) in let*! () = Injector.inject () in + let* () = exit_on_other_proto node_ctxt head in return_unit let install_finalizer node_ctxt rpc_server = @@ -639,12 +680,25 @@ let run ~data_dir ?log_kernel_debug_file (configuration : Configuration.t) ()) configuration.sc_rollup_node_operators in + let*! () = Event.waiting_first_block () in + let*! l1_ctxt = + Layer1.start + ~name:"sc_rollup_node" + ~reconnection_delay:configuration.reconnection_delay + ~protocols:[Protocol.hash] + cctxt + in + let*! head, {shell = {predecessor; _}; _} = Layer1.wait_first l1_ctxt in + let* predecessor = Layer1.fetch_tezos_shell_header cctxt predecessor in + let*! () = Event.received_first_block head in let* node_ctxt = Node_context.init cctxt ~data_dir ?log_kernel_debug_file Read_write + l1_ctxt + ~proto_level:predecessor.proto_level configuration in run node_ctxt configuration diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/event.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/event.ml index 79b27226eef8bf0561b339fc1421099bf9873487..c35631692bfb4f5e25e70842121564d11c104555 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/event.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/event.ml @@ -125,6 +125,38 @@ module Simple = struct for the rollup node." () + let waiting_first_block = + declare_0 + ~section + ~name:"waiting_first_block" + ~level:Notice + ~msg: + (Format.asprintf + "Waiting for first block of protocol %a to appear." + Protocol_hash.pp + Protocol.hash) + () + + let received_first_block = + declare_1 + ~section + ~name:"received_first_block" + ~level:Notice + ~msg: + (Format.asprintf + "First block of protocol %a received: {block}." + Protocol_hash.pp + Protocol.hash) + ("block", Block_hash.encoding) + + let detected_protocol_migration = + declare_0 + ~section + ~name:"detected_protocol_migration" + ~level:Notice + ~msg:"Detected protocol migration, the rollup node will now stop." + () + let acquiring_lock = declare_0 ~section @@ -166,4 +198,11 @@ let kernel_debug_dont_wait msg = let warn_dal_enabled_no_node () = Simple.(emit warn_dal_enabled_no_node) () +let waiting_first_block () = Simple.(emit waiting_first_block) () + +let received_first_block b = Simple.(emit received_first_block) b + +let detected_protocol_migration () = + Simple.(emit detected_protocol_migration) () + let acquiring_lock () = Simple.(emit acquiring_lock) () diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/event.mli b/src/proto_017_PtNairob/lib_sc_rollup_node/event.mli index 188c37b99008a79c9a7fc8fc561b0e57d9223478..cb90a98c4225db9d3cf45f4d1d6f9da755962118 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/event.mli +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/event.mli @@ -76,6 +76,15 @@ val kernel_debug_dont_wait : string -> unit protocol but the rollup node has no DAL node. *) val warn_dal_enabled_no_node : unit -> unit Lwt.t +(** Emit event that the node is waiting for the first block of its protocol. *) +val waiting_first_block : unit -> unit Lwt.t + +(** Emit event that the node received the first block of its protocol. *) +val received_first_block : Block_hash.t -> unit Lwt.t + +(** Emit event that the node will shutdown because of protocol migration. *) +val detected_protocol_migration : unit -> unit Lwt.t + (** [acquiring_lock ()] emits an event to indicate that the node is attempting to acquire a lock on the data directory. *) val acquiring_lock : unit -> unit Lwt.t diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.ml index d344d4301c5480005bc6a60386ed4afb3cb329d1..1dca83cda29d499b1044d4fc7ad371ddfca202cb 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.ml @@ -48,6 +48,7 @@ type 'a t = { pvm : (module Pvm.S); fee_parameters : Configuration.fee_parameters; protocol_constants : Constants.t; + proto_level : int; loser_mode : Loser_mode.t; lockfile : Lwt_unix.file_descr; store : 'a store; @@ -277,7 +278,7 @@ let check_config config = () let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file - mode + mode l1_ctxt ~proto_level Configuration.( { sc_rollup_address = rollup_address; @@ -287,7 +288,6 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file loser_mode; l2_blocks_cache_size; dal_node_endpoint; - reconnection_delay; _; } as configuration) = let open Lwt_result_syntax in @@ -315,9 +315,6 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file Context.load mode (Configuration.default_context_dir data_dir) in let* () = Context.Rollup.check_or_set_address mode context rollup_address in - let* l1_ctxt = - Layer1.start ~name:"sc_rollup_node" ~reconnection_delay cctxt - in let publisher = Configuration.Operator_purpose_map.find Publish operators in let* protocol_constants = retrieve_constants cctxt and* lcc = get_last_cemented_commitment cctxt rollup_address @@ -368,6 +365,7 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file block_finality_time = 2; fee_parameters; protocol_constants; + proto_level; loser_mode; lockfile; store; @@ -986,6 +984,7 @@ module Internal_for_tests = struct block_finality_time = 2; fee_parameters = Configuration.default_fee_parameters; protocol_constants; + proto_level = 0; loser_mode = Loser_mode.no_failures; lockfile; store; diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.mli b/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.mli index bf462508f6a25306508f7423abd67f4c8b14019f..292952b718103fce59d5d07b6d7dd6101bfb427c 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.mli +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.mli @@ -63,6 +63,9 @@ type 'a t = { (** Fee parameters to use when injecting operations in layer 1. *) protocol_constants : Constants.t; (** Protocol constants retrieved from the Tezos node. *) + proto_level : int; + (** Protocol supported by this rollup node (represented as a protocol + level). *) loser_mode : Loser_mode.t; (** If different from [Loser_mode.no_failures], the rollup node issues wrong commitments (for tests). *) @@ -117,15 +120,17 @@ val get_fee_parameter : protocol. *) val protocol_max_batch_size : int -(** [init cctxt ~data_dir mode configuration] initializes the rollup - representation. The rollup origination level and kind are fetched via an RPC - call to the layer1 node that [cctxt] uses for RPC requests. +(** [init cctxt ~data_dir mode l1_ctxt ~proto_level configuration] initializes + the rollup representation. The rollup origination level and kind are fetched + via an RPC call to the layer1 node that [cctxt] uses for RPC requests. *) val init : Protocol_client_context.full -> data_dir:string -> ?log_kernel_debug_file:string -> 'a Store_sigs.mode -> + Layer1.t -> + proto_level:int -> Configuration.t -> 'a t tzresult Lwt.t diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon.ml b/src/proto_alpha/lib_sc_rollup_node/daemon.ml index 082adf54406bcfa334be34de65b6e2ba3cd6858f..6f02adc867c48bf743bfcc772f18c5ea973d7975 100644 --- a/src/proto_alpha/lib_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/lib_sc_rollup_node/daemon.ml @@ -290,6 +290,44 @@ let previous_context (node_ctxt : _ Node_context.t) return (Context.empty node_ctxt.context) else Node_context.checkout_context node_ctxt predecessor.Layer1.hash +let classify_head (node_ctxt : _ Node_context.t) + ?(predecessor : Layer1.header option) (head : Layer1.header) = + let open Lwt_result_syntax in + if head.header.proto_level = node_ctxt.proto_level then + (* Same protocol as supported one, ok *) + return `Supported_proto + else if head.header.proto_level = node_ctxt.proto_level + 1 then + let* predecessor = + match predecessor with + | Some p -> return p + | None -> Node_context.get_predecessor_header node_ctxt head + in + if predecessor.header.proto_level = node_ctxt.proto_level then + (* Migration block from supported protocol to the next one, ok *) + return `Supported_migration + else return `Unsupported_proto + else return `Unsupported_proto + +let exit_on_other_proto node_ctxt ?predecessor head = + let open Lwt_result_syntax in + let* proto_class = classify_head node_ctxt ?predecessor head in + match proto_class with + | `Supported_proto | `Supported_migration -> return_unit + | `Unsupported_proto -> + let*! () = Event.detected_protocol_migration () in + let*! _ = Lwt_exit.exit_and_wait 0 in + return_unit + +let exit_after_proto_migration node_ctxt ?predecessor head = + let open Lwt_result_syntax in + let* proto_class = classify_head node_ctxt ?predecessor head in + match proto_class with + | `Supported_proto -> return_unit + | `Supported_migration | `Unsupported_proto -> + let*! () = Event.detected_protocol_migration () in + let*! _ = Lwt_exit.exit_and_wait 0 in + return_unit + let rec process_head (node_ctxt : _ Node_context.t) (head : Layer1.header) = let open Lwt_result_syntax in let* already_processed = Node_context.is_processed node_ctxt head.hash in @@ -302,6 +340,7 @@ let rec process_head (node_ctxt : _ Node_context.t) (head : Layer1.header) = exist in the chain. *) return_unit | Some predecessor -> + let* () = exit_on_other_proto node_ctxt ~predecessor head in let* () = process_head node_ctxt predecessor in let* ctxt = previous_context node_ctxt ~predecessor in let* () = @@ -433,6 +472,7 @@ let on_layer_1_head node_ctxt (head : Layer1.header) = let* () = Batcher.batch () in let* () = Batcher.new_head stripped_head in let*! () = Injector.inject ~header:head.header () in + let* () = exit_after_proto_migration node_ctxt head in return_unit let daemonize (node_ctxt : _ Node_context.t) = @@ -449,6 +489,7 @@ let degraded_refutation_mode (node_ctxt : _ Node_context.t) = Layer1.iter_heads node_ctxt.l1_ctxt @@ fun head -> let* () = Refutation_coordinator.process (Layer1.head_of_header head) in let*! () = Injector.inject () in + let* () = exit_on_other_proto node_ctxt head in return_unit let install_finalizer node_ctxt rpc_server = @@ -640,12 +681,25 @@ let run ~data_dir ?log_kernel_debug_file (configuration : Configuration.t) ()) configuration.sc_rollup_node_operators in + let*! () = Event.waiting_first_block () in + let*! l1_ctxt = + Layer1.start + ~name:"sc_rollup_node" + ~reconnection_delay:configuration.reconnection_delay + ~protocols:[Protocol.hash] + cctxt + in + let*! head, {shell = {predecessor; _}; _} = Layer1.wait_first l1_ctxt in + let* predecessor = Layer1.fetch_tezos_shell_header cctxt predecessor in + let*! () = Event.received_first_block head in let* node_ctxt = Node_context.init cctxt ~data_dir ?log_kernel_debug_file Read_write + l1_ctxt + ~proto_level:predecessor.proto_level configuration in run node_ctxt configuration diff --git a/src/proto_alpha/lib_sc_rollup_node/event.ml b/src/proto_alpha/lib_sc_rollup_node/event.ml index 79b27226eef8bf0561b339fc1421099bf9873487..c35631692bfb4f5e25e70842121564d11c104555 100644 --- a/src/proto_alpha/lib_sc_rollup_node/event.ml +++ b/src/proto_alpha/lib_sc_rollup_node/event.ml @@ -125,6 +125,38 @@ module Simple = struct for the rollup node." () + let waiting_first_block = + declare_0 + ~section + ~name:"waiting_first_block" + ~level:Notice + ~msg: + (Format.asprintf + "Waiting for first block of protocol %a to appear." + Protocol_hash.pp + Protocol.hash) + () + + let received_first_block = + declare_1 + ~section + ~name:"received_first_block" + ~level:Notice + ~msg: + (Format.asprintf + "First block of protocol %a received: {block}." + Protocol_hash.pp + Protocol.hash) + ("block", Block_hash.encoding) + + let detected_protocol_migration = + declare_0 + ~section + ~name:"detected_protocol_migration" + ~level:Notice + ~msg:"Detected protocol migration, the rollup node will now stop." + () + let acquiring_lock = declare_0 ~section @@ -166,4 +198,11 @@ let kernel_debug_dont_wait msg = let warn_dal_enabled_no_node () = Simple.(emit warn_dal_enabled_no_node) () +let waiting_first_block () = Simple.(emit waiting_first_block) () + +let received_first_block b = Simple.(emit received_first_block) b + +let detected_protocol_migration () = + Simple.(emit detected_protocol_migration) () + let acquiring_lock () = Simple.(emit acquiring_lock) () diff --git a/src/proto_alpha/lib_sc_rollup_node/event.mli b/src/proto_alpha/lib_sc_rollup_node/event.mli index 188c37b99008a79c9a7fc8fc561b0e57d9223478..cb90a98c4225db9d3cf45f4d1d6f9da755962118 100644 --- a/src/proto_alpha/lib_sc_rollup_node/event.mli +++ b/src/proto_alpha/lib_sc_rollup_node/event.mli @@ -76,6 +76,15 @@ val kernel_debug_dont_wait : string -> unit protocol but the rollup node has no DAL node. *) val warn_dal_enabled_no_node : unit -> unit Lwt.t +(** Emit event that the node is waiting for the first block of its protocol. *) +val waiting_first_block : unit -> unit Lwt.t + +(** Emit event that the node received the first block of its protocol. *) +val received_first_block : Block_hash.t -> unit Lwt.t + +(** Emit event that the node will shutdown because of protocol migration. *) +val detected_protocol_migration : unit -> unit Lwt.t + (** [acquiring_lock ()] emits an event to indicate that the node is attempting to acquire a lock on the data directory. *) val acquiring_lock : unit -> unit Lwt.t diff --git a/src/proto_alpha/lib_sc_rollup_node/node_context.ml b/src/proto_alpha/lib_sc_rollup_node/node_context.ml index 733d325c437dbb30acf14d0488a2a81860b8b923..ea2fc259292e58f1d116d83d5b3cb17bb9563578 100644 --- a/src/proto_alpha/lib_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/lib_sc_rollup_node/node_context.ml @@ -50,6 +50,7 @@ type 'a t = { pvm : (module Pvm.S); fee_parameters : Configuration.fee_parameters; protocol_constants : Constants.t; + proto_level : int; loser_mode : Loser_mode.t; lockfile : Lwt_unix.file_descr; store : 'a store; @@ -268,7 +269,7 @@ let check_config config = () let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file - mode + mode l1_ctxt ~proto_level Configuration.( { sc_rollup_address = rollup_address; @@ -279,7 +280,6 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file loser_mode; l2_blocks_cache_size; dal_node_endpoint; - reconnection_delay; _; } as configuration) = let open Lwt_result_syntax in @@ -302,9 +302,6 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file Context.load mode (Configuration.default_context_dir data_dir) in let* () = Context.Rollup.check_or_set_address mode context rollup_address in - let* l1_ctxt = - Layer1.start ~name:"sc_rollup_node" ~reconnection_delay cctxt - in let publisher = Configuration.Operator_purpose_map.find Publish operators in let* protocol_constants = retrieve_constants cctxt and* lcc = get_last_cemented_commitment cctxt rollup_address @@ -369,6 +366,7 @@ let init (cctxt : Protocol_client_context.full) ~data_dir ?log_kernel_debug_file block_finality_time = 2; fee_parameters; protocol_constants; + proto_level; loser_mode; lockfile; store; @@ -991,6 +989,7 @@ module Internal_for_tests = struct block_finality_time = 2; fee_parameters = Configuration.default_fee_parameters; protocol_constants; + proto_level = 0; loser_mode = Loser_mode.no_failures; lockfile; store; diff --git a/src/proto_alpha/lib_sc_rollup_node/node_context.mli b/src/proto_alpha/lib_sc_rollup_node/node_context.mli index 4478dae14e0d8706d02bd33784d64fe382747d46..8d61848a64921deca413a2d6e411c7b7b5d4ea6b 100644 --- a/src/proto_alpha/lib_sc_rollup_node/node_context.mli +++ b/src/proto_alpha/lib_sc_rollup_node/node_context.mli @@ -68,6 +68,9 @@ type 'a t = { (** Fee parameters to use when injecting operations in layer 1. *) protocol_constants : Constants.t; (** Protocol constants retrieved from the Tezos node. *) + proto_level : int; + (** Protocol supported by this rollup node (represented as a protocol + level). *) loser_mode : Loser_mode.t; (** If different from [Loser_mode.no_failures], the rollup node issues wrong commitments (for tests). *) @@ -122,15 +125,17 @@ val get_fee_parameter : protocol. *) val protocol_max_batch_size : int -(** [init cctxt ~data_dir mode configuration] initializes the rollup - representation. The rollup origination level and kind are fetched via an RPC - call to the layer1 node that [cctxt] uses for RPC requests. +(** [init cctxt ~data_dir mode l1_ctxt ~proto_level configuration] initializes + the rollup representation. The rollup origination level and kind are fetched + via an RPC call to the layer1 node that [cctxt] uses for RPC requests. *) val init : Protocol_client_context.full -> data_dir:string -> ?log_kernel_debug_file:string -> 'a Store_sigs.mode -> + Layer1.t -> + proto_level:int -> Configuration.t -> 'a t tzresult Lwt.t diff --git a/tezt/lib_tezos/sc_rollup_node.ml b/tezt/lib_tezos/sc_rollup_node.ml index ca739d3db0af34aa88afe5986a7a9448ac875947..16b3c8641f13fbba88dfc8b43647474905329301 100644 --- a/tezt/lib_tezos/sc_rollup_node.ml +++ b/tezt/lib_tezos/sc_rollup_node.ml @@ -352,8 +352,8 @@ let run ?(legacy = false) ?event_level ?event_sections_levels ?loser_mode node in do_runlike_command ?event_level ?event_sections_levels node cmd -let run ?legacy ?event_level ?event_sections_levels ?loser_mode node - rollup_address arguments = +let run ?legacy ?event_level ?event_sections_levels ?loser_mode + ?(wait_ready = true) node rollup_address arguments = let* () = run ?legacy @@ -364,7 +364,7 @@ let run ?legacy ?event_level ?event_sections_levels ?loser_mode node rollup_address arguments in - let* () = wait_for_ready node in + let* () = if wait_ready then wait_for_ready node else unit in return () let spawn_run node rollup_address extra_arguments = diff --git a/tezt/lib_tezos/sc_rollup_node.mli b/tezt/lib_tezos/sc_rollup_node.mli index 26874680ba95588d53615ec0dfe5a0d82e59cc7e..cdd746fbbea53cf6fe5737038f472d5422521f5a 100644 --- a/tezt/lib_tezos/sc_rollup_node.mli +++ b/tezt/lib_tezos/sc_rollup_node.mli @@ -108,18 +108,20 @@ val base_dir : t -> string If no [msg] is given, the stderr is ignored.*) val check_error : ?exit_code:int -> ?msg:Base.rex -> t -> unit Lwt.t -(** [run ?event_level ?event_sections_levels ?loser_mode node rollup_address - arguments ] launches the given smart contract rollup node for the rollup at - [rollup_address] with the given extra arguments. [event_level] and - [event_sections_levels] allow to select which events we want the node to +(** [run ?event_level ?event_sections_levels ?loser_mode ?wait_ready node + rollup_address arguments ] launches the given smart contract rollup node for + the rollup at [rollup_address] with the given extra arguments. [event_level] + and [event_sections_levels] allow to select which events we want the node to emit (see {!Daemon}). [legacy] (by default [false]) must be set if we want to use the legacy [run] command of the node (which requires a config file to - exist). *) + exist). If [wait_ready] is [false], tezt does not wait for the node to be + ready. *) val run : ?legacy:bool -> ?event_level:Daemon.Level.default_level -> ?event_sections_levels:(string * Daemon.Level.level) list -> ?loser_mode:string -> + ?wait_ready:bool -> t -> string -> string list -> diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 87ddd7a03d80f2ff41cf7686c66b8361cbf88172..252a2bf428feffa95198cb26ba21b193aff44c07 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -383,7 +383,7 @@ let test_l2_migration_scenario ?parameters_ty ?(mode = Sc_rollup_node.Operator) let* tezos_node, tezos_client = setup_l1 ?commitment_period ?challenge_window ?timeout migrate_from in - let* rollup_node, rollup_client, sc_rollup = + let* rollup_node_from, rollup_client_from, sc_rollup = setup_rollup ~protocol:migrate_from ?parameters_ty @@ -394,10 +394,35 @@ let test_l2_migration_scenario ?parameters_ty ?(mode = Sc_rollup_node.Operator) tezos_node tezos_client in + (* Rollup node and client for other protocol *) + let data_dir = Sc_rollup_node.data_dir rollup_node_from in + let rollup_node_to = + Sc_rollup_node.create + ~protocol:migrate_to + ~data_dir + ~base_dir:(Client.base_dir tezos_client) + ~default_operator:operator + mode + tezos_node + in + let rollup_client_to = + Sc_rollup_client.create ~protocol:migrate_to rollup_node_to + in + let* prior_res = - scenario_prior ~sc_rollup rollup_node rollup_client tezos_node tezos_client + scenario_prior + ~sc_rollup + ~rollup_node_from + ~rollup_client_from + ~rollup_node_to + ~rollup_client_to + tezos_node + tezos_client in let migration_level = Node.get_level tezos_node + 1 in + let rollup_node_from_processed_migration_level = + Sc_rollup_node.wait_for_level ~timeout:10. rollup_node_from migration_level + in let patch_config = Node.Config_file.set_sandbox_network_with_user_activated_upgrades [(migration_level, migrate_to)] @@ -413,24 +438,13 @@ let test_l2_migration_scenario ?parameters_ty ?(mode = Sc_rollup_node.Operator) let* () = Node.run ~patch_config tezos_node nodes_args in let* () = Node.wait_for_ready tezos_node in let* () = Client.bake_for_and_wait tezos_client in - (* Rollup node and client for other protocol *) - let data_dir = Sc_rollup_node.data_dir rollup_node in - let rollup_node = - Sc_rollup_node.create - ~protocol:migrate_to - ~data_dir - ~base_dir:(Client.base_dir tezos_client) - ~default_operator:operator - mode - tezos_node - in - let rollup_client = - Sc_rollup_client.create ~protocol:migrate_to rollup_node - in + let* _ = rollup_node_from_processed_migration_level in scenario_after ~sc_rollup - rollup_node - rollup_client + ~rollup_node_from + ~rollup_client_from + ~rollup_node_to + ~rollup_client_to tezos_node tezos_client prior_res @@ -1457,27 +1471,28 @@ let test_rollup_node_simple_migration ~kind ~migrate_from ~migrate_to = let description = "node can read data after store migration" in let commitment_period = 10 in let challenge_window = 10 in - let scenario_prior ~sc_rollup rollup_node _rollup_client _tezos_node - tezos_client = - let* () = Sc_rollup_node.run rollup_node sc_rollup [] in + let scenario_prior ~sc_rollup ~rollup_node_from ~rollup_client_from:_ + ~rollup_node_to ~rollup_client_to:_ _tezos_node tezos_client = + let* () = Sc_rollup_node.run rollup_node_from sc_rollup [] + and* () = + Sc_rollup_node.run ~wait_ready:false rollup_node_to sc_rollup [] + in let* () = send_messages commitment_period tezos_client in - let* _ = Sc_rollup_node.wait_sync rollup_node ~timeout:10. in - let* () = Sc_rollup_node.terminate rollup_node in + let* _ = Sc_rollup_node.wait_sync rollup_node_from ~timeout:10. in unit in - let scenario_after ~sc_rollup rollup_node rollup_client tezos_node - tezos_client () = + let scenario_after ~sc_rollup:_ ~rollup_node_from:_ ~rollup_client_from:_ + ~rollup_node_to ~rollup_client_to tezos_node tezos_client () = let migration_level = Node.get_level tezos_node in - let* () = Sc_rollup_node.run rollup_node sc_rollup [] in + let* () = send_messages 1 tezos_client in + let* _ = Sc_rollup_node.wait_sync rollup_node_to ~timeout:10. in let*! _l2_block = Sc_rollup_client.rpc_get - rollup_client + rollup_client_to ["global"; "block"; string_of_int (migration_level - 1)] in - let* () = send_messages 1 tezos_client in - let* _ = Sc_rollup_node.wait_sync rollup_node ~timeout:10. in let*! _l2_block = - Sc_rollup_client.rpc_get rollup_client ["global"; "block"; "head"] + Sc_rollup_client.rpc_get rollup_client_to ["global"; "block"; "head"] in unit in