diff --git a/src/lib_crawler/layer_1.ml b/src/lib_crawler/layer_1.ml index 9c8d7eedf8ea8d0d216245329810d0faa399365b..70a128dec8325bca4d085d1151b4f6440337568e 100644 --- a/src/lib_crawler/layer_1.ml +++ b/src/lib_crawler/layer_1.ml @@ -229,18 +229,19 @@ let get_predecessor state ((hash, _) as head) = | None -> tzfail (Cannot_find_predecessor hash) | Some pred -> return pred -let nth_predecessor l1_state n block = +let nth_predecessor ~get_predecessor n block = let open Lwt_result_syntax in assert (n >= 0) ; let rec aux acc n block = if n = 0 then return (block, acc) else - let* pred = get_predecessor l1_state block in + let* pred = get_predecessor block in (aux [@tailcall]) (block :: acc) (n - 1) pred in aux [] n block -let get_tezos_reorg_for_new_head l1_state old_head new_head = +let get_tezos_reorg_for_new_head l1_state + ?(get_old_predecessor = get_predecessor l1_state) old_head new_head = let open Lwt_result_syntax in (* old_head and new_head must have the same level when calling aux *) let rec aux reorg old_head new_head = @@ -248,7 +249,7 @@ let get_tezos_reorg_for_new_head l1_state old_head new_head = let new_head_hash, _ = new_head in if Block_hash.(old_head_hash = new_head_hash) then return reorg else - let* old_head_pred = get_predecessor l1_state old_head in + let* old_head_pred = get_old_predecessor old_head in let* new_head_pred = get_predecessor l1_state new_head in let reorg = Reorg. @@ -268,17 +269,25 @@ let get_tezos_reorg_for_new_head l1_state old_head new_head = if old_head_level = new_head_level then return (old_head, new_head, Reorg.no_reorg) else if old_head_level < new_head_level then - let+ new_head, new_chain = nth_predecessor l1_state distance new_head in + let+ new_head, new_chain = + nth_predecessor + ~get_predecessor:(get_predecessor l1_state) + distance + new_head + in (old_head, new_head, {Reorg.no_reorg with new_chain}) else - let+ old_head, old_chain = nth_predecessor l1_state distance old_head in + let+ old_head, old_chain = + nth_predecessor ~get_predecessor:get_old_predecessor distance old_head + in (old_head, new_head, {Reorg.no_reorg with old_chain}) in assert (snd old_head = snd new_head) ; aux reorg old_head new_head (** Returns the reorganization of L1 blocks (if any) for [new_head]. *) -let get_tezos_reorg_for_new_head l1_state old_head new_head = +let get_tezos_reorg_for_new_head l1_state ?get_old_predecessor old_head new_head + = let open Lwt_result_syntax in match old_head with | `Level l -> @@ -288,9 +297,14 @@ let get_tezos_reorg_for_new_head l1_state old_head new_head = else let* _block_at_l, new_chain = nth_predecessor - l1_state + ~get_predecessor:(get_predecessor l1_state) (Int32.sub new_head_level l |> Int32.to_int) new_head in return Reorg.{old_chain = []; new_chain} - | `Head old_head -> get_tezos_reorg_for_new_head l1_state old_head new_head + | `Head old_head -> + get_tezos_reorg_for_new_head + l1_state + ?get_old_predecessor + old_head + new_head diff --git a/src/lib_crawler/layer_1.mli b/src/lib_crawler/layer_1.mli index f3f549bc551d696fee053555a73b18244993c418..e03581a8734d54af6c86ab7ed45882b6dc373491 100644 --- a/src/lib_crawler/layer_1.mli +++ b/src/lib_crawler/layer_1.mli @@ -27,6 +27,8 @@ (** This module allow to follow the layer 1 chain by subscribing to the head monitoring RPC offered by the Tezos node, reconnecting, etc. *) +type error += Cannot_find_predecessor of Block_hash.t + (** The type of layer 1 followers. *) type t @@ -66,21 +68,28 @@ val get_predecessor_opt : val get_predecessor : t -> Block_hash.t * int32 -> (Block_hash.t * int32) tzresult Lwt.t -(** [nth_predecessor l1_ctxt n head] returns [block, history] where [block] is - the nth predecessor of [head] and [history] is the list of blocks between - [block] (excluded) and [head] (included) on the chain *) +(** [nth_predecessor ~get_predecessor n head] returns [block, history] where + [block] is the nth predecessor of [head] and [history] is the list of blocks + between [block] (excluded) and [head] (included) on the chain. It uses the + function [get_predecessor] to retrieve the predecessor of one block. *) val nth_predecessor : - t -> + get_predecessor:('a -> 'a tzresult Lwt.t) -> int -> - Block_hash.t * int32 -> - ((Block_hash.t * int32) * (Block_hash.t * int32) list) tzresult Lwt.t + 'a -> + ('a * 'a trace) tzresult Lwt.t -(** [get_tezos_reorg_for_new_head l1_ctxt old_head new_head] returns the - reorganization of L1 blocks between [old_head] and [new_head]. If [old_head] - is [`Level l], then it returns the reorganization between [new_head] and - level [l] on the same chain. *) +(** [get_tezos_reorg_for_new_head l1_ctxt ?get_old_predecessor old_head + new_head] returns the reorganization of L1 blocks between [old_head] and + [new_head]. If [old_head] is [`Level l], then it returns the reorganization + between [new_head] and level [l] on the same chain. [get_old_predecessor] + can be provided to use a different function (than by default + {!get_predecessor}) to retrieve the predecessors of the old head. This is + necessary when the old head is not in the L1 chain anymore and forgotten by + the L1 node. *) val get_tezos_reorg_for_new_head : t -> + ?get_old_predecessor: + (Block_hash.t * int32 -> (Block_hash.t * int32) tzresult Lwt.t) -> [`Head of Block_hash.t * int32 | `Level of int32] -> Block_hash.t * int32 -> (Block_hash.t * int32) Reorg.t tzresult Lwt.t diff --git a/src/lib_injector/injector_events.ml b/src/lib_injector/injector_events.ml index acc4a3d134b1db7bef4acf50c01721f546332484..ff6ff1062819155ff3fc2a4850fbbb7eeb7b6098 100644 --- a/src/lib_injector/injector_events.ml +++ b/src/lib_injector/injector_events.ml @@ -129,6 +129,13 @@ module Make ~level:Debug ("head", Block_hash.encoding) + let cannot_compute_reorg = + declare_1 + ~name:"cannot_compute_reorg" + ~msg:"Cannot compute reorg for new block {head}" + ~level:Warning + ("head", Block_hash.encoding) + let injecting_pending = declare_1 ~name:"injecting_pending" diff --git a/src/lib_injector/injector_functor.ml b/src/lib_injector/injector_functor.ml index 884284926019a6d203bd1633d3a615fde965ec96..729265213423d7d5ca85cae14edb2a8548178c5b 100644 --- a/src/lib_injector/injector_functor.ml +++ b/src/lib_injector/injector_functor.ml @@ -922,7 +922,7 @@ struct let on_new_tezos_head state ((head_hash, head_level) as head) = let open Lwt_result_syntax in let*! () = Event.(emit1 new_tezos_head) state head_hash in - let* reorg = + let*! reorg = match state.last_seen_head with | None -> return {Reorg.no_reorg with new_chain = [head]} | Some last_head -> @@ -931,6 +931,25 @@ struct (`Head last_head) head in + let* reorg = + match reorg with + | Error trace + when TzTrace.fold + (fun yes error -> + yes + || + match error with + | Layer_1.Cannot_find_predecessor _ -> true + | _ -> false) + false + trace -> + (* The reorganization could not be computed entirely because of + missing info on the Layer 1. We may miss on some backtracking but + it is better than to fail. *) + let*! () = Event.(emit1 cannot_compute_reorg) state head_hash in + return {Reorg.no_reorg with new_chain = [head]} + | _ -> Lwt.return reorg + in let* () = List.iter_es (fun (removed_block, _) -> 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 f824fd99cf8ddeee08ee603c5bf3d6f662ef3583..9f067c83c4a4154f1d852c84713af9471e53b2a7 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml @@ -299,7 +299,7 @@ module Make (PVM : Pvm.S) = struct in unless (already_finalized || before_origination node_ctxt block) @@ fun () -> - let* predecessor = Layer1.get_predecessor_opt node_ctxt.l1_ctxt block in + let* predecessor = Node_context.get_predecessor_opt node_ctxt block in let* () = Option.iter_es (processed_finalized_block node_ctxt) predecessor in @@ -308,69 +308,85 @@ module Make (PVM : Pvm.S) = struct let* () = Node_context.mark_finalized_head node_ctxt hash in return_unit - let process_head (node_ctxt : _ Node_context.t) Layer1.({hash; level} as head) - = + let rec process_head (node_ctxt : _ Node_context.t) + Layer1.({hash; level} as head) = let open Lwt_result_syntax in + let* already_processed = Node_context.is_processed node_ctxt hash in + unless (already_processed || before_origination node_ctxt head) @@ fun () -> let*! () = Daemon_event.head_processing hash level ~finalized:false in - let* () = Node_context.save_level node_ctxt head in - let* inbox_hash, inbox, inbox_witness, messages, ctxt = - Inbox.process_head node_ctxt head - in - let* () = - when_ (Node_context.dal_supported node_ctxt) @@ fun () -> - Dal_slots_tracker.process_head node_ctxt head - in - let* () = process_l1_block_operations ~finalized:false node_ctxt head in - (* Avoid storing and publishing commitments if the head is not final. *) - (* Avoid triggering the pvm execution if this has been done before for - this head. *) - let* ctxt, _num_messages, num_ticks, initial_tick = - Components.Interpreter.process_head node_ctxt ctxt head (inbox, messages) - in - let*! context_hash = Context.commit ctxt in - let* {Layer1.hash = predecessor; _} = - Layer1.get_predecessor node_ctxt.l1_ctxt head - in - let* commitment_hash = - Components.Commitment.process_head node_ctxt ~predecessor head ctxt - in - let level = Raw_level.of_int32_exn level in - let* previous_commitment_hash = - if level = node_ctxt.genesis_info.Sc_rollup.Commitment.level then - (* Previous commitment for rollup genesis is itself. *) - return node_ctxt.genesis_info.Sc_rollup.Commitment.commitment_hash - else - let+ pred = Node_context.get_l2_block node_ctxt predecessor in - Sc_rollup_block.most_recent_commitment pred.header - in - let header = - Sc_rollup_block. - { - block_hash = hash; - level; - predecessor; - commitment_hash; - previous_commitment_hash; - context = context_hash; - inbox_witness; - inbox_hash; - } - in - let l2_block = - Sc_rollup_block.{header; content = (); num_ticks; initial_tick} - in - let* finalized_block, _ = - Layer1.nth_predecessor - node_ctxt.l1_ctxt - node_ctxt.block_finality_time - head - in - let* () = processed_finalized_block node_ctxt finalized_block in - let* () = Node_context.save_l2_head node_ctxt l2_block in - let*! () = - Daemon_event.new_head_processed hash (Raw_level.to_int32 level) - in - return_unit + let* predecessor = Node_context.get_predecessor_opt node_ctxt head in + match predecessor with + | None -> + (* Predecessor not available on the L1, which means the block does not + exist in the chain. *) + return_unit + | Some predecessor -> + let* () = process_head node_ctxt predecessor in + let* () = Node_context.save_level node_ctxt head in + let* inbox_hash, inbox, inbox_witness, messages, ctxt = + Inbox.process_head node_ctxt ~predecessor head + in + let* () = + when_ (Node_context.dal_supported node_ctxt) @@ fun () -> + Dal_slots_tracker.process_head node_ctxt head + in + let* () = process_l1_block_operations ~finalized:false node_ctxt head in + (* Avoid storing and publishing commitments if the head is not final. *) + (* Avoid triggering the pvm execution if this has been done before for + this head. *) + let* ctxt, _num_messages, num_ticks, initial_tick = + Components.Interpreter.process_head + node_ctxt + ctxt + ~predecessor + head + (inbox, messages) + in + let*! context_hash = Context.commit ctxt in + let* commitment_hash = + Components.Commitment.process_head + node_ctxt + ~predecessor:predecessor.hash + head + ctxt + in + let level = Raw_level.of_int32_exn level in + let* previous_commitment_hash = + if level = node_ctxt.genesis_info.Sc_rollup.Commitment.level then + (* Previous commitment for rollup genesis is itself. *) + return node_ctxt.genesis_info.Sc_rollup.Commitment.commitment_hash + else + let+ pred = Node_context.get_l2_block node_ctxt predecessor.hash in + Sc_rollup_block.most_recent_commitment pred.header + in + let header = + Sc_rollup_block. + { + block_hash = hash; + level; + predecessor = predecessor.hash; + commitment_hash; + previous_commitment_hash; + context = context_hash; + inbox_witness; + inbox_hash; + } + in + let l2_block = + Sc_rollup_block.{header; content = (); num_ticks; initial_tick} + in + let* finalized_block, _ = + Node_context.nth_predecessor + node_ctxt + node_ctxt.block_finality_time + head + in + let* () = processed_finalized_block node_ctxt finalized_block in + let* () = Node_context.save_l2_head node_ctxt l2_block in + let*! () = + Daemon_event.new_head_processed hash (Raw_level.to_int32 level) + in + return_unit (* [on_layer_1_head node_ctxt head] processes a new head from the L1. It also processes any missing blocks that were not processed. Every time a @@ -396,8 +412,25 @@ module Make (PVM : Pvm.S) = struct in `Level (Int32.pred origination_level) in - let* reorg = - Layer1.get_tezos_reorg_for_new_head node_ctxt.l1_ctxt old_head head + let*! reorg = + Node_context.get_tezos_reorg_for_new_head node_ctxt old_head head + in + let*? reorg = + match reorg with + | Error trace + when TzTrace.fold + (fun yes error -> + yes + || + match error with + | Octez_crawler.Layer_1.Cannot_find_predecessor _ -> true + | _ -> false) + false + trace -> + (* The reorganization could not be computed entirely because of missing + info on the Layer 1. We fallback to a recursive process_head. *) + Ok {Reorg.no_reorg with new_chain = [head]} + | _ -> reorg in (* TODO: https://gitlab.com/tezos/tezos/-/issues/3348 Rollback state information on reorganization, i.e. for diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/dal_slots_tracker.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/dal_slots_tracker.ml index 26239d7de02276888fa6ae2fffb1d5470f83282d..e1469af4c414cd54e18a9e750b056a0ec3816548 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/dal_slots_tracker.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/dal_slots_tracker.ml @@ -27,15 +27,15 @@ open Protocol open Alpha_context module Block_services = Block_services.Make (Protocol) (Protocol) -let ancestor_hash ~number_of_levels {Node_context.genesis_info; l1_ctxt; _} head - = +let ancestor_hash ~number_of_levels + ({Node_context.genesis_info; _} as node_ctxt) head = let genesis_level = genesis_info.level in let rec go number_of_levels (Layer1.{hash; level} as head) = let open Lwt_result_syntax in if level < Raw_level.to_int32 genesis_level then return_none else if number_of_levels = 0 then return_some hash else - let* pred_head = Layer1.get_predecessor_opt l1_ctxt head in + let* pred_head = Node_context.get_predecessor_opt node_ctxt head in match pred_head with | None -> return_none | Some pred_head -> go (number_of_levels - 1) pred_head @@ -320,8 +320,8 @@ module Confirmed_slots_history = struct @@ Dal.Slots_history.History_cache.empty ~capacity:(Int64.of_int @@ (num_slots * 60000))) - let update (Node_context.{l1_ctxt; _} as node_ctxt) - Layer1.({hash = head_hash; _} as head) confirmation_info = + let update node_ctxt Layer1.({hash = head_hash; _} as head) confirmation_info + = let open Lwt_result_syntax in let* slots_to_save = confirmed_slots_with_headers node_ctxt confirmation_info @@ -333,7 +333,7 @@ module Confirmed_slots_history = struct Slot_index.compare a b) slots_to_save in - let* pred = Layer1.get_predecessor l1_ctxt head in + let* pred = Node_context.get_predecessor node_ctxt head in let* slots_history = slots_history_of_hash node_ctxt pred in let* slots_cache = slots_history_cache_of_hash node_ctxt pred in let*? slots_history, slots_cache = 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 572791baf5c3a159007d28fbb059b3b704b7b466..e788af5f0d550f289876f366d56bac152667540a 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.ml @@ -120,8 +120,8 @@ let add_messages ~predecessor_timestamp ~predecessor inbox messages = inbox, messages_with_protocol_internal_messages ) -let process_head (node_ctxt : _ Node_context.t) - Layer1.({level; hash = head_hash} as head) = +let process_head (node_ctxt : _ Node_context.t) ~predecessor + Layer1.{level; hash = head_hash} = let open Lwt_result_syntax in let first_inbox_level = Raw_level.to_int32 node_ctxt.genesis_info.level |> Int32.succ @@ -134,7 +134,6 @@ let process_head (node_ctxt : _ Node_context.t) to chain reorganization. *) - let* predecessor = Layer1.get_predecessor node_ctxt.l1_ctxt head in let* inbox = Node_context.inbox_of_head node_ctxt predecessor in let inbox_metrics = Metrics.Inbox.metrics in Prometheus.Gauge.set inbox_metrics.head_inbox_level @@ Int32.to_float level ; 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 f1b33285c0769b4c631d2d2ee2717a2822b9b010..b078741d65da8da37bb05efa8aa77558e9586255 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/inbox.mli @@ -36,11 +36,13 @@ open Protocol.Alpha_context open Sc_rollup -(** [process_head node_ctxt head operations] changes the state of the inbox to - react to [head]. In particular, this process filters the provided +(** [process_head node_ctxt ~predecessor head operations] changes the state of + the inbox to react to [head] (where [predecessor] is the predecessor of + [head] in the L1 chain). In particular, this process filters the provided [operations] of the [head] block. *) val process_head : Node_context.rw -> + predecessor:Layer1.head -> Layer1.head -> (Sc_rollup.Inbox.Hash.t * Sc_rollup.Inbox.t diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/interpreter.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/interpreter.ml index 47242dd61817eff65379d72fe856eeb0c24fde7b..f74691dd333627f70222a95456528b04a351ed23 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/interpreter.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/interpreter.ml @@ -38,6 +38,7 @@ module type S = sig val process_head : Node_context.rw -> 'a Context.t -> + predecessor:Layer1.head -> Layer1.head -> Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> ('a Context.t * int * int64 * Sc_rollup.Tick.t) tzresult Lwt.t @@ -162,16 +163,15 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct in return (ctxt, num_messages, num_ticks, initial_tick) - (** [process_head node_ctxt head] runs the PVM for the given head. *) - let process_head (node_ctxt : _ Node_context.t) ctxt head inbox_messages = + (** [process_head node_ctxt ctxt ~predecessor head] runs the PVM for the given + head. *) + let process_head (node_ctxt : _ Node_context.t) ctxt ~predecessor head + inbox_messages = let open Lwt_result_syntax in let first_inbox_level = Raw_level.to_int32 node_ctxt.genesis_info.level |> Int32.succ in if head.Layer1.level >= first_inbox_level then - let* predecessor = - Layer1.get_predecessor node_ctxt.Node_context.l1_ctxt head - in transition_pvm node_ctxt ctxt predecessor head inbox_messages else if head.Layer1.level = Raw_level.to_int32 node_ctxt.genesis_info.level then diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/interpreter.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/interpreter.mli index 18281abb76f28d17fec42853273ca1f28d9aaa93..71cdbcabd249910063015f09d45fc8b4219353f3 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/interpreter.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/interpreter.mli @@ -34,16 +34,18 @@ module type S = sig module Free_pvm : Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Free.t - (** [process_head node_ctxt head (inbox, messages)] interprets the [messages] - associated with a [head]. This requires the [inbox] to be updated - beforehand. It returns [(ctxt, num_messages, num_ticks, tick)] where - [ctxt] is the updated layer 2 context (with the new PVM state), + (** [process_head node_ctxt ~predecessor head (inbox, messages)] interprets + the [messages] associated with a [head] (where [predecessor] is the + predecessor of [head] in the L1 chain). This requires the [inbox] to be + updated beforehand. It returns [(ctxt, num_messages, num_ticks, tick)] + where [ctxt] is the updated layer 2 context (with the new PVM state), [num_messages] is the number of [messages], [num_ticks] is the number of ticks taken by the PVM for the evaluation and [tick] is the tick reached by the PVM after the evaluation. *) val process_head : Node_context.rw -> 'a Context.t -> + predecessor:Layer1.head -> Layer1.head -> Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> ('a Context.t * int * int64 * Sc_rollup.Tick.t) tzresult Lwt.t diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/layer1.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/layer1.ml index d11aa61d64c00e7f5974c871508f410e27247098..0c515602b8c258546bf1c156b0b6b6d2ae7bb263 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/layer1.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/layer1.ml @@ -78,40 +78,9 @@ let blocks_cache : blocks_cache = Blocks_cache.create 32 include Octez_crawler.Layer_1 -let head_of_block_level (hash, level) = {hash; level} - -let block_level_of_head {hash; level} = (hash, level) - let iter_heads l1_ctxt f = iter_heads l1_ctxt @@ fun (hash, {shell = {level; _}; _}) -> f {hash; level} -let get_predecessor_opt state head = - let open Lwt_result_syntax in - let+ res = get_predecessor_opt state (block_level_of_head head) in - Option.map head_of_block_level res - -let get_predecessor state head = - let open Lwt_result_syntax in - let+ res = get_predecessor state (block_level_of_head head) in - head_of_block_level res - -let nth_predecessor l1_state n block = - let open Lwt_result_syntax in - let+ res, preds = nth_predecessor l1_state n (block_level_of_head block) in - (head_of_block_level res, List.map head_of_block_level preds) - -let get_tezos_reorg_for_new_head l1_ctxt old_head new_head = - let open Lwt_result_syntax in - let old_head = - match old_head with - | `Level l -> `Level l - | `Head h -> `Head (block_level_of_head h) - in - let+ reorg = - get_tezos_reorg_for_new_head l1_ctxt old_head (block_level_of_head new_head) - in - Reorg.map head_of_block_level reorg - (** Helpers diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/layer1.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/layer1.mli index 1580d1b26f023c61852796180b044bdbc0fbd25f..c9697e4fe5c0eae830ca054d187a4db7489a3f50 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/layer1.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/layer1.mli @@ -47,25 +47,6 @@ val iter_heads : t -> (head -> unit tzresult Lwt.t) -> unit tzresult Lwt.t (** {2 Helpers } *) -(** [get_predecessor_opt state head] returns the predecessor of block [head], - when [head] is not the genesis block. *) -val get_predecessor_opt : t -> head -> head option tzresult Lwt.t - -(** [get_predecessor state head] returns the predecessor block of [head]. *) -val get_predecessor : t -> head -> head tzresult Lwt.t - -(** [nth_predecessor l1_ctxt n head] return [block, history] where [block] is - the nth predecessor of [head] and [history] is the list of blocks between - [block] (excluded) and [head] (included) on the chain *) -val nth_predecessor : t -> int -> head -> (head * head list) tzresult Lwt.t - -(** [get_tezos_reorg_for_new_head l1_ctxt old_head new_head] returns the - reorganization of L1 blocks between [old_head] and [new_head]. If [old_head] - is [`Level l], then it returns the reorganization between [new_head] and - level [l] on the same chain. *) -val get_tezos_reorg_for_new_head : - t -> [`Head of head | `Level of int32] -> head -> head Reorg.t tzresult Lwt.t - (** [fetch_tezos_shell_header cctxt hash] returns the block shell header of [hash]. Looks for the block in the blocks cache first, and fetches it from the L1 node otherwise. *) 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 d17a6af774ad525aa46b1537490890da8d82db48..6f99993ff62faa058ae77230879bf57b6f10ee74 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 @@ -1,7 +1,8 @@ (*****************************************************************************) (* *) (* Open Source License *) -(* Copyright (c) 2022 TriliTech *) +(* 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"),*) @@ -330,6 +331,72 @@ let find_l2_block_by_level node_ctxt level = | None -> return_none | Some block_hash -> find_l2_block node_ctxt block_hash +let head_of_block_level (hash, level) = {Layer1.hash; level} + +let block_level_of_head Layer1.{hash; level} = (hash, level) + +let get_l2_block_predecessor node_ctxt hash = + let open Lwt_result_syntax in + let+ header = Store.L2_blocks.header node_ctxt.store.l2_blocks hash in + Option.map + (fun {Sc_rollup_block.predecessor; level; _} -> + (predecessor, Int32.pred (Raw_level.to_int32 level))) + header + +let get_predecessor_opt node_ctxt (hash, level) = + let open Lwt_result_syntax in + let* pred = get_l2_block_predecessor node_ctxt hash in + match pred with + | Some p -> return_some p + | None -> + (* [head] is not already known in the L2 chain *) + Layer1.get_predecessor_opt node_ctxt.l1_ctxt (hash, level) + +let get_predecessor node_ctxt (hash, level) = + let open Lwt_result_syntax in + let* pred = get_l2_block_predecessor node_ctxt hash in + match pred with + | Some p -> return p + | None -> + (* [head] is not already known in the L2 chain *) + Layer1.get_predecessor node_ctxt.l1_ctxt (hash, level) + +let nth_predecessor node_ctxt n block = + let open Lwt_result_syntax in + let+ res, preds = + Layer1.nth_predecessor + ~get_predecessor:(get_predecessor node_ctxt) + n + (block_level_of_head block) + in + (head_of_block_level res, List.map head_of_block_level preds) + +let get_tezos_reorg_for_new_head node_ctxt old_head new_head = + let open Lwt_result_syntax in + let old_head = + match old_head with + | `Level l -> `Level l + | `Head h -> `Head (block_level_of_head h) + in + let+ reorg = + Layer1.get_tezos_reorg_for_new_head + node_ctxt.l1_ctxt + ~get_old_predecessor:(get_predecessor node_ctxt) + old_head + (block_level_of_head new_head) + in + Reorg.map head_of_block_level reorg + +let get_predecessor_opt node_ctxt head = + let open Lwt_result_syntax in + let+ res = get_predecessor_opt node_ctxt (block_level_of_head head) in + Option.map head_of_block_level res + +let get_predecessor node_ctxt head = + let open Lwt_result_syntax in + let+ res = get_predecessor node_ctxt (block_level_of_head head) in + head_of_block_level res + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4128 Unit test the function tick_search. *) 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 5d896e7faa78b50056f57531252f5690f5c176f2..1f976a65d56062dbc8c1b4df207a3791d3b5f25c 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 @@ -1,7 +1,8 @@ (*****************************************************************************) (* *) (* Open Source License *) -(* Copyright (c) 2022 TriliTech *) +(* 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"),*) @@ -196,6 +197,28 @@ val hash_of_level_opt : _ t -> int32 -> Block_hash.t option tzresult Lwt.t if it is known by the Tezos Layer 1 node. *) val level_of_hash : _ t -> Block_hash.t -> int32 tzresult Lwt.t +(** [get_predecessor_opt state head] returns the predecessor of block [head], + when [head] is not the genesis block. *) +val get_predecessor_opt : + _ t -> Layer1.head -> Layer1.head option tzresult Lwt.t + +(** [get_predecessor state head] returns the predecessor block of [head]. *) +val get_predecessor : _ t -> Layer1.head -> Layer1.head tzresult Lwt.t + +(** [nth_predecessor n head] return [block, history] where [block] is the nth + predecessor of [head] and [history] is the list of blocks between [block] + (excluded) and [head] (included) on the chain. *) +val nth_predecessor : + _ t -> int -> Layer1.head -> (Layer1.head * Layer1.head list) tzresult Lwt.t + +(** [get_tezos_reorg_for_new_head node_ctxt old_head new_head] returns the L1 + reorganization between [old_head] and [new_head]. *) +val get_tezos_reorg_for_new_head : + _ t -> + [`Head of Layer1.head | `Level of int32] -> + Layer1.head -> + Layer1.head Reorg.t tzresult Lwt.t + (** [block_with_tick store ~max_level tick] returns [Some b] where [b] is the last layer 2 block which contains the [tick] before [max_level]. If no such block exists (the tick happened after [max_level], or we are too late), the diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon.ml b/src/proto_alpha/lib_sc_rollup_node/daemon.ml index 8c9fd495a64d1aba67aab9573913a3999d793e4b..6c40166adcd20200092a7010db52470b3eb9b679 100644 --- a/src/proto_alpha/lib_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/lib_sc_rollup_node/daemon.ml @@ -296,7 +296,7 @@ module Make (PVM : Pvm.S) = struct in unless (already_finalized || before_origination node_ctxt block) @@ fun () -> - let* predecessor = Layer1.get_predecessor_opt node_ctxt.l1_ctxt block in + let* predecessor = Node_context.get_predecessor_opt node_ctxt block in let* () = Option.iter_es (processed_finalized_block node_ctxt) predecessor in @@ -305,69 +305,85 @@ module Make (PVM : Pvm.S) = struct let* () = Node_context.mark_finalized_head node_ctxt hash in return_unit - let process_head (node_ctxt : _ Node_context.t) Layer1.({hash; level} as head) - = + let rec process_head (node_ctxt : _ Node_context.t) + Layer1.({hash; level} as head) = let open Lwt_result_syntax in + let* already_processed = Node_context.is_processed node_ctxt hash in + unless (already_processed || before_origination node_ctxt head) @@ fun () -> let*! () = Daemon_event.head_processing hash level ~finalized:false in - let* () = Node_context.save_level node_ctxt head in - let* inbox_hash, inbox, inbox_witness, messages, ctxt = - Inbox.process_head node_ctxt head - in - let* () = - when_ (Node_context.dal_supported node_ctxt) @@ fun () -> - Dal_slots_tracker.process_head node_ctxt head - in - let* () = process_l1_block_operations ~finalized:false node_ctxt head in - (* Avoid storing and publishing commitments if the head is not final. *) - (* Avoid triggering the pvm execution if this has been done before for - this head. *) - let* ctxt, _num_messages, num_ticks, initial_tick = - Components.Interpreter.process_head node_ctxt ctxt head (inbox, messages) - in - let*! context_hash = Context.commit ctxt in - let* {Layer1.hash = predecessor; _} = - Layer1.get_predecessor node_ctxt.l1_ctxt head - in - let* commitment_hash = - Components.Commitment.process_head node_ctxt ~predecessor head ctxt - in - let level = Raw_level.of_int32_exn level in - let* previous_commitment_hash = - if level = node_ctxt.genesis_info.Sc_rollup.Commitment.level then - (* Previous commitment for rollup genesis is itself. *) - return node_ctxt.genesis_info.Sc_rollup.Commitment.commitment_hash - else - let+ pred = Node_context.get_l2_block node_ctxt predecessor in - Sc_rollup_block.most_recent_commitment pred.header - in - let header = - Sc_rollup_block. - { - block_hash = hash; - level; - predecessor; - commitment_hash; - previous_commitment_hash; - context = context_hash; - inbox_witness; - inbox_hash; - } - in - let l2_block = - Sc_rollup_block.{header; content = (); num_ticks; initial_tick} - in - let* finalized_block, _ = - Layer1.nth_predecessor - node_ctxt.l1_ctxt - node_ctxt.block_finality_time - head - in - let* () = processed_finalized_block node_ctxt finalized_block in - let* () = Node_context.save_l2_head node_ctxt l2_block in - let*! () = - Daemon_event.new_head_processed hash (Raw_level.to_int32 level) - in - return_unit + let* predecessor = Node_context.get_predecessor_opt node_ctxt head in + match predecessor with + | None -> + (* Predecessor not available on the L1, which means the block does not + exist in the chain. *) + return_unit + | Some predecessor -> + let* () = process_head node_ctxt predecessor in + let* () = Node_context.save_level node_ctxt head in + let* inbox_hash, inbox, inbox_witness, messages, ctxt = + Inbox.process_head node_ctxt ~predecessor head + in + let* () = + when_ (Node_context.dal_supported node_ctxt) @@ fun () -> + Dal_slots_tracker.process_head node_ctxt head + in + let* () = process_l1_block_operations ~finalized:false node_ctxt head in + (* Avoid storing and publishing commitments if the head is not final. *) + (* Avoid triggering the pvm execution if this has been done before for + this head. *) + let* ctxt, _num_messages, num_ticks, initial_tick = + Components.Interpreter.process_head + node_ctxt + ctxt + ~predecessor + head + (inbox, messages) + in + let*! context_hash = Context.commit ctxt in + let* commitment_hash = + Components.Commitment.process_head + node_ctxt + ~predecessor:predecessor.hash + head + ctxt + in + let level = Raw_level.of_int32_exn level in + let* previous_commitment_hash = + if level = node_ctxt.genesis_info.Sc_rollup.Commitment.level then + (* Previous commitment for rollup genesis is itself. *) + return node_ctxt.genesis_info.Sc_rollup.Commitment.commitment_hash + else + let+ pred = Node_context.get_l2_block node_ctxt predecessor.hash in + Sc_rollup_block.most_recent_commitment pred.header + in + let header = + Sc_rollup_block. + { + block_hash = hash; + level; + predecessor = predecessor.hash; + commitment_hash; + previous_commitment_hash; + context = context_hash; + inbox_witness; + inbox_hash; + } + in + let l2_block = + Sc_rollup_block.{header; content = (); num_ticks; initial_tick} + in + let* finalized_block, _ = + Node_context.nth_predecessor + node_ctxt + node_ctxt.block_finality_time + head + in + let* () = processed_finalized_block node_ctxt finalized_block in + let* () = Node_context.save_l2_head node_ctxt l2_block in + let*! () = + Daemon_event.new_head_processed hash (Raw_level.to_int32 level) + in + return_unit (* [on_layer_1_head node_ctxt head] processes a new head from the L1. It also processes any missing blocks that were not processed. Every time a @@ -393,8 +409,25 @@ module Make (PVM : Pvm.S) = struct in `Level (Int32.pred origination_level) in - let* reorg = - Layer1.get_tezos_reorg_for_new_head node_ctxt.l1_ctxt old_head head + let*! reorg = + Node_context.get_tezos_reorg_for_new_head node_ctxt old_head head + in + let*? reorg = + match reorg with + | Error trace + when TzTrace.fold + (fun yes error -> + yes + || + match error with + | Octez_crawler.Layer_1.Cannot_find_predecessor _ -> true + | _ -> false) + false + trace -> + (* The reorganization could not be computed entirely because of missing + info on the Layer 1. We fallback to a recursive process_head. *) + Ok {Reorg.no_reorg with new_chain = [head]} + | _ -> reorg in (* TODO: https://gitlab.com/tezos/tezos/-/issues/3348 Rollback state information on reorganization, i.e. for diff --git a/src/proto_alpha/lib_sc_rollup_node/dal_slots_tracker.ml b/src/proto_alpha/lib_sc_rollup_node/dal_slots_tracker.ml index f372fad3e147d99c86ca983f258898e2bed9f0fa..67fcbebfcedfe9efaf5b99b45fc7d539f2f6996f 100644 --- a/src/proto_alpha/lib_sc_rollup_node/dal_slots_tracker.ml +++ b/src/proto_alpha/lib_sc_rollup_node/dal_slots_tracker.ml @@ -27,15 +27,15 @@ open Protocol open Alpha_context module Block_services = Block_services.Make (Protocol) (Protocol) -let ancestor_hash ~number_of_levels {Node_context.genesis_info; l1_ctxt; _} head - = +let ancestor_hash ~number_of_levels + ({Node_context.genesis_info; _} as node_ctxt) head = let genesis_level = genesis_info.level in let rec go number_of_levels (Layer1.{hash; level} as head) = let open Lwt_result_syntax in if level < Raw_level.to_int32 genesis_level then return_none else if number_of_levels = 0 then return_some hash else - let* pred_head = Layer1.get_predecessor_opt l1_ctxt head in + let* pred_head = Node_context.get_predecessor_opt node_ctxt head in match pred_head with | None -> return_none | Some pred_head -> go (number_of_levels - 1) pred_head @@ -289,8 +289,8 @@ module Confirmed_slots_history = struct @@ Dal.Slots_history.History_cache.empty ~capacity:(Int64.of_int @@ (num_slots * 60000))) - let update (Node_context.{l1_ctxt; _} as node_ctxt) - Layer1.({hash = head_hash; _} as head) confirmation_info = + let update node_ctxt Layer1.({hash = head_hash; _} as head) confirmation_info + = let open Lwt_result_syntax in let* slots_to_save = confirmed_slots_with_headers node_ctxt confirmation_info @@ -302,7 +302,7 @@ module Confirmed_slots_history = struct Slot_index.compare a b) slots_to_save in - let* pred = Layer1.get_predecessor l1_ctxt head in + let* pred = Node_context.get_predecessor node_ctxt head in let* slots_history = slots_history_of_hash node_ctxt pred in let* slots_cache = slots_history_cache_of_hash node_ctxt pred in let*? slots_history, slots_cache = diff --git a/src/proto_alpha/lib_sc_rollup_node/inbox.ml b/src/proto_alpha/lib_sc_rollup_node/inbox.ml index c2415f9f5bbd4ba9477fe8f8c60dba56746c65dd..ae42c820870e13b706e0b1a5db2aa2f553fcc3c9 100644 --- a/src/proto_alpha/lib_sc_rollup_node/inbox.ml +++ b/src/proto_alpha/lib_sc_rollup_node/inbox.ml @@ -133,8 +133,8 @@ let add_messages ~is_migration_block ~predecessor_timestamp ~predecessor inbox inbox, messages_with_protocol_internal_messages ) -let process_head (node_ctxt : _ Node_context.t) - Layer1.({level; hash = head_hash} as head) = +let process_head (node_ctxt : _ Node_context.t) ~predecessor + Layer1.{level; hash = head_hash} = let open Lwt_result_syntax in let first_inbox_level = Raw_level.to_int32 node_ctxt.genesis_info.level |> Int32.succ @@ -147,7 +147,6 @@ let process_head (node_ctxt : _ Node_context.t) to chain reorganization. *) - let* predecessor = Layer1.get_predecessor node_ctxt.l1_ctxt head in let* inbox = Node_context.inbox_of_head node_ctxt predecessor in let inbox_metrics = Metrics.Inbox.metrics in Prometheus.Gauge.set inbox_metrics.head_inbox_level @@ Int32.to_float level ; diff --git a/src/proto_alpha/lib_sc_rollup_node/inbox.mli b/src/proto_alpha/lib_sc_rollup_node/inbox.mli index ecf8e3f83406fea0ad691bd66962303dd5c3cfe8..23c035774f3a6a6824d7a16f456b1ed4d7b4ee8c 100644 --- a/src/proto_alpha/lib_sc_rollup_node/inbox.mli +++ b/src/proto_alpha/lib_sc_rollup_node/inbox.mli @@ -36,11 +36,13 @@ open Protocol.Alpha_context open Sc_rollup -(** [process_head node_ctxt head operations] changes the state of the inbox to - react to [head]. In particular, this process filters the provided +(** [process_head node_ctxt ~predecessor head operations] changes the state of + the inbox to react to [head] (where [predecessor] is the predecessor of + [head] in the L1 chain). In particular, this process filters the provided [operations] of the [head] block. *) val process_head : Node_context.rw -> + predecessor:Layer1.head -> Layer1.head -> (Sc_rollup.Inbox.Hash.t * Sc_rollup.Inbox.t diff --git a/src/proto_alpha/lib_sc_rollup_node/interpreter.ml b/src/proto_alpha/lib_sc_rollup_node/interpreter.ml index 9a4890a7e852501396f4475f63f3821928010b03..b3f5f911fe2f71b1c16981571af35cada149483a 100644 --- a/src/proto_alpha/lib_sc_rollup_node/interpreter.ml +++ b/src/proto_alpha/lib_sc_rollup_node/interpreter.ml @@ -38,6 +38,7 @@ module type S = sig val process_head : Node_context.rw -> 'a Context.t -> + predecessor:Layer1.head -> Layer1.head -> Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> ('a Context.t * int * int64 * Sc_rollup.Tick.t) tzresult Lwt.t @@ -162,16 +163,15 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct in return (ctxt, num_messages, num_ticks, initial_tick) - (** [process_head node_ctxt head] runs the PVM for the given head. *) - let process_head (node_ctxt : _ Node_context.t) ctxt head inbox_messages = + (** [process_head node_ctxt ctxt ~predecessor head] runs the PVM for the given + head. *) + let process_head (node_ctxt : _ Node_context.t) ctxt ~predecessor head + inbox_messages = let open Lwt_result_syntax in let first_inbox_level = Raw_level.to_int32 node_ctxt.genesis_info.level |> Int32.succ in if head.Layer1.level >= first_inbox_level then - let* predecessor = - Layer1.get_predecessor node_ctxt.Node_context.l1_ctxt head - in transition_pvm node_ctxt ctxt predecessor head inbox_messages else if head.Layer1.level = Raw_level.to_int32 node_ctxt.genesis_info.level then diff --git a/src/proto_alpha/lib_sc_rollup_node/interpreter.mli b/src/proto_alpha/lib_sc_rollup_node/interpreter.mli index 18281abb76f28d17fec42853273ca1f28d9aaa93..71cdbcabd249910063015f09d45fc8b4219353f3 100644 --- a/src/proto_alpha/lib_sc_rollup_node/interpreter.mli +++ b/src/proto_alpha/lib_sc_rollup_node/interpreter.mli @@ -34,16 +34,18 @@ module type S = sig module Free_pvm : Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Free.t - (** [process_head node_ctxt head (inbox, messages)] interprets the [messages] - associated with a [head]. This requires the [inbox] to be updated - beforehand. It returns [(ctxt, num_messages, num_ticks, tick)] where - [ctxt] is the updated layer 2 context (with the new PVM state), + (** [process_head node_ctxt ~predecessor head (inbox, messages)] interprets + the [messages] associated with a [head] (where [predecessor] is the + predecessor of [head] in the L1 chain). This requires the [inbox] to be + updated beforehand. It returns [(ctxt, num_messages, num_ticks, tick)] + where [ctxt] is the updated layer 2 context (with the new PVM state), [num_messages] is the number of [messages], [num_ticks] is the number of ticks taken by the PVM for the evaluation and [tick] is the tick reached by the PVM after the evaluation. *) val process_head : Node_context.rw -> 'a Context.t -> + predecessor:Layer1.head -> Layer1.head -> Sc_rollup.Inbox.t * Sc_rollup.Inbox_message.t list -> ('a Context.t * int * int64 * Sc_rollup.Tick.t) tzresult Lwt.t diff --git a/src/proto_alpha/lib_sc_rollup_node/layer1.ml b/src/proto_alpha/lib_sc_rollup_node/layer1.ml index 2bbaf1c0710e05c2ae7cbfec947376bef2438050..d32f5b6d5c3f3d25c5170f6d11526c279a9d6239 100644 --- a/src/proto_alpha/lib_sc_rollup_node/layer1.ml +++ b/src/proto_alpha/lib_sc_rollup_node/layer1.ml @@ -78,40 +78,9 @@ let blocks_cache : blocks_cache = Blocks_cache.create 32 include Octez_crawler.Layer_1 -let head_of_block_level (hash, level) = {hash; level} - -let block_level_of_head {hash; level} = (hash, level) - let iter_heads l1_ctxt f = iter_heads l1_ctxt @@ fun (hash, {shell = {level; _}; _}) -> f {hash; level} -let get_predecessor_opt state head = - let open Lwt_result_syntax in - let+ res = get_predecessor_opt state (block_level_of_head head) in - Option.map head_of_block_level res - -let get_predecessor state head = - let open Lwt_result_syntax in - let+ res = get_predecessor state (block_level_of_head head) in - head_of_block_level res - -let nth_predecessor l1_state n block = - let open Lwt_result_syntax in - let+ res, preds = nth_predecessor l1_state n (block_level_of_head block) in - (head_of_block_level res, List.map head_of_block_level preds) - -let get_tezos_reorg_for_new_head l1_ctxt old_head new_head = - let open Lwt_result_syntax in - let old_head = - match old_head with - | `Level l -> `Level l - | `Head h -> `Head (block_level_of_head h) - in - let+ reorg = - get_tezos_reorg_for_new_head l1_ctxt old_head (block_level_of_head new_head) - in - Reorg.map head_of_block_level reorg - (** Helpers diff --git a/src/proto_alpha/lib_sc_rollup_node/layer1.mli b/src/proto_alpha/lib_sc_rollup_node/layer1.mli index 1580d1b26f023c61852796180b044bdbc0fbd25f..c9697e4fe5c0eae830ca054d187a4db7489a3f50 100644 --- a/src/proto_alpha/lib_sc_rollup_node/layer1.mli +++ b/src/proto_alpha/lib_sc_rollup_node/layer1.mli @@ -47,25 +47,6 @@ val iter_heads : t -> (head -> unit tzresult Lwt.t) -> unit tzresult Lwt.t (** {2 Helpers } *) -(** [get_predecessor_opt state head] returns the predecessor of block [head], - when [head] is not the genesis block. *) -val get_predecessor_opt : t -> head -> head option tzresult Lwt.t - -(** [get_predecessor state head] returns the predecessor block of [head]. *) -val get_predecessor : t -> head -> head tzresult Lwt.t - -(** [nth_predecessor l1_ctxt n head] return [block, history] where [block] is - the nth predecessor of [head] and [history] is the list of blocks between - [block] (excluded) and [head] (included) on the chain *) -val nth_predecessor : t -> int -> head -> (head * head list) tzresult Lwt.t - -(** [get_tezos_reorg_for_new_head l1_ctxt old_head new_head] returns the - reorganization of L1 blocks between [old_head] and [new_head]. If [old_head] - is [`Level l], then it returns the reorganization between [new_head] and - level [l] on the same chain. *) -val get_tezos_reorg_for_new_head : - t -> [`Head of head | `Level of int32] -> head -> head Reorg.t tzresult Lwt.t - (** [fetch_tezos_shell_header cctxt hash] returns the block shell header of [hash]. Looks for the block in the blocks cache first, and fetches it from the L1 node otherwise. *) 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 5827936498c5d0195035787925fd0567a4d070f5..93b1838964d106b0780d765fa60b7f8f8d3636b5 100644 --- a/src/proto_alpha/lib_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/lib_sc_rollup_node/node_context.ml @@ -1,7 +1,8 @@ (*****************************************************************************) (* *) (* Open Source License *) -(* Copyright (c) 2022 TriliTech *) +(* 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"),*) @@ -330,6 +331,72 @@ let find_l2_block_by_level node_ctxt level = | None -> return_none | Some block_hash -> find_l2_block node_ctxt block_hash +let head_of_block_level (hash, level) = {Layer1.hash; level} + +let block_level_of_head Layer1.{hash; level} = (hash, level) + +let get_l2_block_predecessor node_ctxt hash = + let open Lwt_result_syntax in + let+ header = Store.L2_blocks.header node_ctxt.store.l2_blocks hash in + Option.map + (fun {Sc_rollup_block.predecessor; level; _} -> + (predecessor, Int32.pred (Raw_level.to_int32 level))) + header + +let get_predecessor_opt node_ctxt (hash, level) = + let open Lwt_result_syntax in + let* pred = get_l2_block_predecessor node_ctxt hash in + match pred with + | Some p -> return_some p + | None -> + (* [head] is not already known in the L2 chain *) + Layer1.get_predecessor_opt node_ctxt.l1_ctxt (hash, level) + +let get_predecessor node_ctxt (hash, level) = + let open Lwt_result_syntax in + let* pred = get_l2_block_predecessor node_ctxt hash in + match pred with + | Some p -> return p + | None -> + (* [head] is not already known in the L2 chain *) + Layer1.get_predecessor node_ctxt.l1_ctxt (hash, level) + +let nth_predecessor node_ctxt n block = + let open Lwt_result_syntax in + let+ res, preds = + Layer1.nth_predecessor + ~get_predecessor:(get_predecessor node_ctxt) + n + (block_level_of_head block) + in + (head_of_block_level res, List.map head_of_block_level preds) + +let get_tezos_reorg_for_new_head node_ctxt old_head new_head = + let open Lwt_result_syntax in + let old_head = + match old_head with + | `Level l -> `Level l + | `Head h -> `Head (block_level_of_head h) + in + let+ reorg = + Layer1.get_tezos_reorg_for_new_head + node_ctxt.l1_ctxt + ~get_old_predecessor:(get_predecessor node_ctxt) + old_head + (block_level_of_head new_head) + in + Reorg.map head_of_block_level reorg + +let get_predecessor_opt node_ctxt head = + let open Lwt_result_syntax in + let+ res = get_predecessor_opt node_ctxt (block_level_of_head head) in + Option.map head_of_block_level res + +let get_predecessor node_ctxt head = + let open Lwt_result_syntax in + let+ res = get_predecessor node_ctxt (block_level_of_head head) in + head_of_block_level res + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4128 Unit test the function tick_search. *) 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 b62ddddfe2b5195c2e708b2472371ccf3176ea1a..20a8070e8bde3f115f54f08f3fddbef18d8ada0d 100644 --- a/src/proto_alpha/lib_sc_rollup_node/node_context.mli +++ b/src/proto_alpha/lib_sc_rollup_node/node_context.mli @@ -1,7 +1,8 @@ (*****************************************************************************) (* *) (* Open Source License *) -(* Copyright (c) 2022 TriliTech *) +(* 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"),*) @@ -194,6 +195,28 @@ val hash_of_level_opt : _ t -> int32 -> Block_hash.t option tzresult Lwt.t if it is known by the Tezos Layer 1 node. *) val level_of_hash : _ t -> Block_hash.t -> int32 tzresult Lwt.t +(** [get_predecessor_opt state head] returns the predecessor of block [head], + when [head] is not the genesis block. *) +val get_predecessor_opt : + _ t -> Layer1.head -> Layer1.head option tzresult Lwt.t + +(** [get_predecessor state head] returns the predecessor block of [head]. *) +val get_predecessor : _ t -> Layer1.head -> Layer1.head tzresult Lwt.t + +(** [nth_predecessor n head] return [block, history] where [block] is the nth + predecessor of [head] and [history] is the list of blocks between [block] + (excluded) and [head] (included) on the chain. *) +val nth_predecessor : + _ t -> int -> Layer1.head -> (Layer1.head * Layer1.head list) tzresult Lwt.t + +(** [get_tezos_reorg_for_new_head node_ctxt old_head new_head] returns the L1 + reorganization between [old_head] and [new_head]. *) +val get_tezos_reorg_for_new_head : + _ t -> + [`Head of Layer1.head | `Level of int32] -> + Layer1.head -> + Layer1.head Reorg.t tzresult Lwt.t + (** [block_with_tick store ~max_level tick] returns [Some b] where [b] is the last layer 2 block which contains the [tick] before [max_level]. If no such block exists (the tick happened after [max_level], or we are too late), the diff --git a/tezt/lib_tezos/sc_rollup_node.ml b/tezt/lib_tezos/sc_rollup_node.ml index 6e374f9adce1a3faf17f3f213706f2efc3729d4d..0639453e58636455260b0ea509dd81c20d2a5c08 100644 --- a/tezt/lib_tezos/sc_rollup_node.ml +++ b/tezt/lib_tezos/sc_rollup_node.ml @@ -37,7 +37,7 @@ module Parameters = struct rpc_port : int; mode : mode; dal_node : Dal_node.t option; - node : Node.t; + mutable node : Node.t; mutable pending_ready : unit option Lwt.u list; mutable pending_level : (int * int option Lwt.u) list; runner : Runner.t option; @@ -323,3 +323,8 @@ let run node arguments = let* () = run node arguments in let* () = wait_for_ready node in return () + +let change_node_and_restart sc_rollup_node node = + let* () = terminate sc_rollup_node in + sc_rollup_node.persistent_state.node <- node ; + run sc_rollup_node [] diff --git a/tezt/lib_tezos/sc_rollup_node.mli b/tezt/lib_tezos/sc_rollup_node.mli index 8097f5c6b37832813ea1937e5347cfbf59dc49b3..f50f8534ff72d4f837ab966fb951b5ce25bd666d 100644 --- a/tezt/lib_tezos/sc_rollup_node.mli +++ b/tezt/lib_tezos/sc_rollup_node.mli @@ -170,3 +170,7 @@ val wait_for_level : ?timeout:float -> t -> int -> int Lwt.t returned. [where], if present, should describe the constraint that [filter] applies. *) val wait_for : ?where:string -> t -> string -> (JSON.t -> 'a option) -> 'a Lwt.t + +(** Stops the rollup node and restart it, connected to another Tezos Layer 1 + node. *) +val change_node_and_restart : t -> Node.t -> unit Lwt.t diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 99f0e860353b746480a1d4298f641073edebb4bc..b0d250ebf561bfa3d1a5702a9420ef16fc3f42e8 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -1629,8 +1629,8 @@ let commitment_stored_robust_to_failures protocol sc_rollup_node (Some stored_commitment', "stored in second node") ; unit -let commitments_reorgs ~kind _protocol sc_rollup_node sc_rollup_client sc_rollup - node client = +let commitments_reorgs ~switch_l1_node ~kind _protocol sc_rollup_node + sc_rollup_client sc_rollup node client = (* No messages are published after origination, for `sc_rollup_commitment_period_in_blocks - 1` levels. Then a divergence occurs: in the first branch one message is published for @@ -1710,7 +1710,17 @@ let commitments_reorgs ~kind _protocol sc_rollup_node sc_rollup_client sc_rollup in let* () = divergence () in - let* () = trigger_reorg () in + let* client, node = + if switch_l1_node then ( + (* Switch the L1 node of a rollup node so that reverted blocks are not *) + (* available in the new L1 node. *) + Log.info "Changing L1 node for rollup node" ; + let* () = Sc_rollup_node.change_node_and_restart sc_rollup_node node' in + return (client', node')) + else + let* () = trigger_reorg () in + return (client, node) + in (* After triggering a reorganisation the node should see that there is a more attractive head at level `init_level + sc_rollup_commitment_period_in_blocks + block_finality_time - 1`. @@ -4340,7 +4350,12 @@ let register ~kind ~protocols = ~kind ; test_commitment_scenario ~variant:"handles_chain_reorgs" - (commitments_reorgs ~kind) + (commitments_reorgs ~kind ~switch_l1_node:false) + protocols + ~kind ; + test_commitment_scenario + ~variant:"handles_chain_reorgs_missing_blocks" + (commitments_reorgs ~kind ~switch_l1_node:true) protocols ~kind ; test_commitment_scenario