From fd530a1d14be60aa7bafe3bffd31a410c26cef33 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 22 Aug 2023 08:26:54 +0200 Subject: [PATCH 1/7] SCORU/Node: fix setting head of reproposal --- src/lib_smart_rollup_node/node_context.ml | 17 +++-- src/lib_smart_rollup_node/node_context.mli | 9 ++- .../rollup_node_daemon.ml | 64 +++++++++++++------ 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/lib_smart_rollup_node/node_context.ml b/src/lib_smart_rollup_node/node_context.ml index 0cd1596f2fbf..f15e9ffa96d7 100644 --- a/src/lib_smart_rollup_node/node_context.ml +++ b/src/lib_smart_rollup_node/node_context.ml @@ -330,16 +330,15 @@ let level_of_hash {l1_ctxt; store; _} hash = let save_level {store; _} Layer1.{hash; level} = Store.Levels_to_hashes.add store.levels_to_hashes level hash -let save_l2_head {store; _} (head : Sc_rollup_block.t) = - let open Lwt_result_syntax in +let save_l2_block {store; _} (head : Sc_rollup_block.t) = let head_info = {head with header = (); content = ()} in - let* () = - Store.L2_blocks.append - store.l2_blocks - ~key:head.header.block_hash - ~header:head.header - ~value:head_info - in + Store.L2_blocks.append + store.l2_blocks + ~key:head.header.block_hash + ~header:head.header + ~value:head_info + +let set_l2_head {store; _} (head : Sc_rollup_block.t) = Store.L2_head.write store.l2_head head let is_processed {store; _} head = Store.L2_blocks.mem store.l2_blocks head diff --git a/src/lib_smart_rollup_node/node_context.mli b/src/lib_smart_rollup_node/node_context.mli index 7ab1a18f9482..084ab26d9440 100644 --- a/src/lib_smart_rollup_node/node_context.mli +++ b/src/lib_smart_rollup_node/node_context.mli @@ -211,9 +211,12 @@ val get_full_l2_block : head.hash] in the store. *) val save_level : rw -> Layer1.head -> unit tzresult Lwt.t -(** [save_l2_head t l2_block] remembers that the [l2_block.head] is - processed. The system should not have to come back to it. *) -val save_l2_head : rw -> Sc_rollup_block.t -> unit tzresult Lwt.t +(** [save_l2_block t l2_block] remembers that the [l2_block] is processed. The + system should not have to come back to it. *) +val save_l2_block : rw -> Sc_rollup_block.t -> unit tzresult Lwt.t + +(** [set_l2_head t l2_block] sets [l2_block] as the new head of the L2 chain. *) +val set_l2_head : rw -> Sc_rollup_block.t -> unit tzresult Lwt.t (** [last_processed_head_opt store] returns the last processed head if it exists. *) diff --git a/src/lib_smart_rollup_node/rollup_node_daemon.ml b/src/lib_smart_rollup_node/rollup_node_daemon.ml index 3523e3468df6..9183d0eaa9a9 100644 --- a/src/lib_smart_rollup_node/rollup_node_daemon.ml +++ b/src/lib_smart_rollup_node/rollup_node_daemon.ml @@ -90,7 +90,7 @@ let handle_protocol_migration ~catching_up state (head : Layer1.header) = (* Process a L1 that we have never seen and for which we have processed the predecessor. *) -let process_new_head ({node_ctxt; _} as state) ~catching_up ~predecessor +let process_unseen_head ({node_ctxt; _} as state) ~catching_up ~predecessor (head : Layer1.header) = let open Lwt_result_syntax in let* () = Node_context.save_protocol_info node_ctxt head ~predecessor in @@ -160,10 +160,38 @@ let process_new_head ({node_ctxt; _} as state) ~catching_up ~predecessor node_ctxt Int32.(sub head.level (of_int node_ctxt.block_finality_time)) in - let* () = Node_context.save_l2_head node_ctxt l2_block in - return_unit + let* () = Node_context.save_l2_block node_ctxt l2_block in + return l2_block + +let rec process_l1_block ({node_ctxt; _} as state) ~catching_up + (head : Layer1.header) = + let open Lwt_result_syntax in + if is_before_origination node_ctxt head then return `Nothing + else + let* l2_head = Node_context.find_l2_block node_ctxt head.hash in + match l2_head with + | Some l2_head -> + (* Already processed *) + return (`Already_processed l2_head) + | None -> ( + (* New head *) + let*! () = Daemon_event.head_processing head.hash head.level in + let* predecessor = + Node_context.get_predecessor_header_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 `Nothing + | Some predecessor -> + let* () = update_l2_chain state ~catching_up:true predecessor in + let* l2_head = + process_unseen_head state ~catching_up ~predecessor head + in + return (`New l2_head)) -let rec process_head ({node_ctxt; _} as state) ~catching_up +and update_l2_chain ({node_ctxt; _} as state) ~catching_up (head : Layer1.header) = let open Lwt_result_syntax in let start_timestamp = Time.System.now () in @@ -172,19 +200,12 @@ let rec process_head ({node_ctxt; _} as state) ~catching_up node_ctxt {Layer1.hash = head.hash; level = head.level} in - let* already_processed = Node_context.is_processed node_ctxt head.hash in - unless (already_processed || is_before_origination node_ctxt head) - @@ fun () -> - let*! () = Daemon_event.head_processing head.hash head.level in - let* predecessor = Node_context.get_predecessor_header_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 state ~catching_up:true predecessor in - let* () = process_new_head state ~catching_up ~predecessor head in + let* done_ = process_l1_block state ~catching_up head in + match done_ with + | `Nothing -> return_unit + | `Already_processed l2_block -> Node_context.set_l2_head node_ctxt l2_block + | `New l2_block -> + let* () = Node_context.set_l2_head node_ctxt l2_block in let stop_timestamp = Time.System.now () in let process_time = Ptime.diff stop_timestamp start_timestamp in Metrics.Inbox.set_process_time process_time ; @@ -225,7 +246,7 @@ let on_layer_1_head ({node_ctxt; _} as state) (head : Layer1.header) = false trace -> (* The reorganization could not be computed entirely because of missing - info on the Layer 1. We fallback to a recursive process_head. *) + info on the Layer 1. We fallback to a recursive process_l1_block. *) Ok {Reorg.no_reorg with new_chain = [stripped_head]} | _ -> reorg in @@ -251,7 +272,7 @@ let on_layer_1_head ({node_ctxt; _} as state) (head : Layer1.header) = to_prefetch ; let* header = get_header block in let catching_up = block.level < head.level in - process_head state ~catching_up header) + update_l2_chain state ~catching_up header) new_chain_prefetching in let module Plugin = (val state.plugin) in @@ -438,7 +459,7 @@ let run ({node_ctxt; configuration; plugin; _} as state) = | e -> error_to_degraded_mode e) module Internal_for_tests = struct - (** Same as {!process_head} but only builds and stores the L2 block + (** Same as {!update_l2_chain} but only builds and stores the L2 block corresponding to [messages]. It is used by the unit tests to build an L2 chain. *) let process_messages (module Plugin : Protocol_plugin_sig.S) @@ -498,7 +519,8 @@ module Internal_for_tests = struct let l2_block = Sc_rollup_block.{header; content = (); num_ticks; initial_tick} in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* () = Node_context.save_l2_block node_ctxt l2_block in + let* () = Node_context.set_l2_head node_ctxt l2_block in return l2_block end -- GitLab From 4f2396828ed6ca05d291577c31e48e2a2d26a1d7 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 22 Aug 2023 08:59:02 +0200 Subject: [PATCH 2/7] SCORU/Node/Alpha: fix setting head of reproposal --- src/proto_alpha/lib_sc_rollup_node/daemon.ml | 225 ++++++++++-------- .../test/helpers/helpers.ml | 3 +- 2 files changed, 131 insertions(+), 97 deletions(-) diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon.ml b/src/proto_alpha/lib_sc_rollup_node/daemon.ml index a73ed48a9aee..8eba504f4d6b 100644 --- a/src/proto_alpha/lib_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/lib_sc_rollup_node/daemon.ml @@ -80,111 +80,143 @@ let exit_after_proto_migration node_ctxt ?predecessor head = let*! _ = Lwt_exit.exit_and_wait 0 in return_unit -let rec process_head (daemon_components : (module Daemon_components.S)) +let rec process_l1_block (daemon_components : (module Daemon_components.S)) (node_ctxt : _ Node_context.t) ~catching_up (head : Layer1.header) = let open Lwt_result_syntax in + if before_origination node_ctxt head then return `Nothing + else + let* l2_head = Node_context.find_l2_block node_ctxt head.hash in + match l2_head with + | Some l2_head -> + (* Already processed *) + return (`Already_processed l2_head) + | None -> ( + (* New head *) + let*! () = Daemon_event.head_processing head.hash head.level in + let* predecessor = + Node_context.get_predecessor_header_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 `Nothing + | Some predecessor -> + let* () = exit_on_other_proto node_ctxt ~predecessor head in + let* () = + update_l2_chain + daemon_components + node_ctxt + ~catching_up:true + predecessor + in + let* ctxt = previous_context node_ctxt ~predecessor in + let* () = + Node_context.save_protocol_info node_ctxt head ~predecessor + in + let* inbox_hash, inbox, inbox_witness, messages = + Inbox.process_head node_ctxt ~predecessor head + in + let inbox_witness = + Sc_rollup_proto_types.Merkelized_payload_hashes_hash.to_octez + inbox_witness + in + let inbox_hash = + Sc_rollup_proto_types.Inbox_hash.to_octez inbox_hash + in + let* () = + when_ (Node_context.dal_supported node_ctxt) @@ fun () -> + Dal_slots_tracker.process_head + node_ctxt + (Layer1.head_of_header head) + in + let* () = process_l1_block_operations node_ctxt head in + (* Avoid storing and publishing commitments if the head is not final. *) + (* Avoid triggering the pvm execution if this has been done before for + this head. *) + let* ctxt, _num_messages, num_ticks, initial_tick = + Interpreter.process_head + (module Rollup_node_plugin.Plugin) + node_ctxt + ctxt + ~predecessor + head + (inbox, messages) + in + let*! context_hash = Context.commit ctxt in + let* commitment_hash = + Publisher.process_head + (module Rollup_node_plugin.Plugin) + node_ctxt + ~predecessor:predecessor.hash + head + ctxt + in + let commitment_hash = + Option.map + Sc_rollup_proto_types.Commitment_hash.to_octez + commitment_hash + in + let* () = + unless (catching_up && Option.is_none commitment_hash) + @@ fun () -> Inbox.same_as_layer_1 node_ctxt head.hash inbox + in + let level = head.level in + let* previous_commitment_hash = + if level = node_ctxt.genesis_info.level then + (* Previous commitment for rollup genesis is itself. *) + return node_ctxt.genesis_info.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 = head.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* () = + Node_context.mark_finalized_level + node_ctxt + Int32.(sub head.level (of_int node_ctxt.block_finality_time)) + in + let* () = Node_context.save_l2_block node_ctxt l2_block in + return (`New l2_block)) + +and update_l2_chain daemon_components node_ctxt ~catching_up + (head : Layer1.header) = + let open Lwt_result_syntax in let start_timestamp = Time.System.now () in let* () = Node_context.save_level node_ctxt {Layer1.hash = head.hash; level = head.level} in - let* already_processed = Node_context.is_processed node_ctxt head.hash in - unless (already_processed || before_origination node_ctxt head) @@ fun () -> - let*! () = Daemon_event.head_processing head.hash head.level in - let* predecessor = Node_context.get_predecessor_header_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* () = exit_on_other_proto node_ctxt ~predecessor head in - let* () = - process_head daemon_components node_ctxt ~catching_up:true predecessor - in - let* ctxt = previous_context node_ctxt ~predecessor in - let* () = Node_context.save_protocol_info node_ctxt head ~predecessor in - let* inbox_hash, inbox, inbox_witness, messages = - Inbox.process_head node_ctxt ~predecessor head - in - let inbox_witness = - Sc_rollup_proto_types.Merkelized_payload_hashes_hash.to_octez - inbox_witness - in - let inbox_hash = Sc_rollup_proto_types.Inbox_hash.to_octez inbox_hash in - let* () = - when_ (Node_context.dal_supported node_ctxt) @@ fun () -> - Dal_slots_tracker.process_head node_ctxt (Layer1.head_of_header head) - in - let* () = process_l1_block_operations node_ctxt head in - (* Avoid storing and publishing commitments if the head is not final. *) - (* Avoid triggering the pvm execution if this has been done before for - this head. *) - let* ctxt, _num_messages, num_ticks, initial_tick = - Interpreter.process_head - (module Rollup_node_plugin.Plugin) - node_ctxt - ctxt - ~predecessor - head - (inbox, messages) - in - let*! context_hash = Context.commit ctxt in - let* commitment_hash = - Publisher.process_head - (module Rollup_node_plugin.Plugin) - node_ctxt - ~predecessor:predecessor.hash - head - ctxt - in - let commitment_hash = - Option.map - Sc_rollup_proto_types.Commitment_hash.to_octez - commitment_hash - in - let* () = - unless (catching_up && Option.is_none commitment_hash) @@ fun () -> - Inbox.same_as_layer_1 node_ctxt head.hash inbox - in - let level = head.level in - let* previous_commitment_hash = - if level = node_ctxt.genesis_info.level then - (* Previous commitment for rollup genesis is itself. *) - return node_ctxt.genesis_info.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 = head.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* () = - Node_context.mark_finalized_level - node_ctxt - Int32.(sub head.level (of_int node_ctxt.block_finality_time)) - in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* done_ = process_l1_block daemon_components node_ctxt ~catching_up head in + match done_ with + | `Nothing -> return_unit + | `Already_processed l2_block -> Node_context.set_l2_head node_ctxt l2_block + | `New l2_block -> + let* () = Node_context.set_l2_head node_ctxt l2_block in let stop_timestamp = Time.System.now () in let process_time = Ptime.diff stop_timestamp start_timestamp in + Metrics.Inbox.set_process_time process_time ; let*! () = Daemon_event.new_head_processed head.hash head.level process_time in - Metrics.Inbox.set_process_time process_time ; return_unit (* [on_layer_1_head node_ctxt head] processes a new head from the L1. It @@ -221,7 +253,7 @@ let on_layer_1_head (daemon_components : (module Daemon_components.S)) node_ctxt false trace -> (* The reorganization could not be computed entirely because of missing - info on the Layer 1. We fallback to a recursive process_head. *) + info on the Layer 1. We fallback to a recursive process_l1_block. *) Ok {Reorg.no_reorg with new_chain = [stripped_head]} | _ -> reorg in @@ -244,7 +276,7 @@ let on_layer_1_head (daemon_components : (module Daemon_components.S)) node_ctxt Layer1_helpers.prefetch_tezos_blocks node_ctxt.l1_ctxt to_prefetch ; let* header = get_header block in let catching_up = block.level < head.level in - process_head daemon_components node_ctxt ~catching_up header) + update_l2_chain daemon_components node_ctxt ~catching_up header) new_chain_prefetching in let* () = Publisher.publish_commitments () in @@ -423,7 +455,7 @@ let run node_ctxt configuration | e -> error_to_degraded_mode e) module Internal_for_tests = struct - (** Same as {!process_head} but only builds and stores the L2 block + (** Same as {!update_l2_chain} but only builds and stores the L2 block corresponding to [messages]. It is used by the unit tests to build an L2 chain. *) let process_messages (node_ctxt : _ Node_context.t) ~is_first_block @@ -490,7 +522,8 @@ module Internal_for_tests = struct let l2_block = Sc_rollup_block.{header; content = (); num_ticks; initial_tick} in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* () = Node_context.save_l2_block node_ctxt l2_block in + let* () = Node_context.set_l2_head node_ctxt l2_block in return l2_block end diff --git a/src/proto_alpha/lib_sc_rollup_node/test/helpers/helpers.ml b/src/proto_alpha/lib_sc_rollup_node/test/helpers/helpers.ml index 90604b57c821..425a37783aa7 100644 --- a/src/proto_alpha/lib_sc_rollup_node/test/helpers/helpers.ml +++ b/src/proto_alpha/lib_sc_rollup_node/test/helpers/helpers.ml @@ -114,7 +114,8 @@ let add_l2_genesis_block (node_ctxt : _ Node_context.t) ~boot_sector = initial_tick = Sc_rollup.Tick.to_z initial_tick; } in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* () = Node_context.save_l2_block node_ctxt l2_block in + let* () = Node_context.set_l2_head node_ctxt l2_block in return l2_block let initialize_node_context ?(constants = default_constants) kind ~boot_sector = -- GitLab From 6bfbbbca5ddaa658d220342ad8dd59ed2fee1e87 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 22 Aug 2023 08:59:12 +0200 Subject: [PATCH 3/7] SCORU/Node/Oxford: fix setting head of reproposal --- .../lib_sc_rollup_node/daemon.ml | 225 ++++++++++-------- .../test/helpers/helpers.ml | 3 +- 2 files changed, 131 insertions(+), 97 deletions(-) diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/daemon.ml b/src/proto_018_Proxford/lib_sc_rollup_node/daemon.ml index a73ed48a9aee..8eba504f4d6b 100644 --- a/src/proto_018_Proxford/lib_sc_rollup_node/daemon.ml +++ b/src/proto_018_Proxford/lib_sc_rollup_node/daemon.ml @@ -80,111 +80,143 @@ let exit_after_proto_migration node_ctxt ?predecessor head = let*! _ = Lwt_exit.exit_and_wait 0 in return_unit -let rec process_head (daemon_components : (module Daemon_components.S)) +let rec process_l1_block (daemon_components : (module Daemon_components.S)) (node_ctxt : _ Node_context.t) ~catching_up (head : Layer1.header) = let open Lwt_result_syntax in + if before_origination node_ctxt head then return `Nothing + else + let* l2_head = Node_context.find_l2_block node_ctxt head.hash in + match l2_head with + | Some l2_head -> + (* Already processed *) + return (`Already_processed l2_head) + | None -> ( + (* New head *) + let*! () = Daemon_event.head_processing head.hash head.level in + let* predecessor = + Node_context.get_predecessor_header_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 `Nothing + | Some predecessor -> + let* () = exit_on_other_proto node_ctxt ~predecessor head in + let* () = + update_l2_chain + daemon_components + node_ctxt + ~catching_up:true + predecessor + in + let* ctxt = previous_context node_ctxt ~predecessor in + let* () = + Node_context.save_protocol_info node_ctxt head ~predecessor + in + let* inbox_hash, inbox, inbox_witness, messages = + Inbox.process_head node_ctxt ~predecessor head + in + let inbox_witness = + Sc_rollup_proto_types.Merkelized_payload_hashes_hash.to_octez + inbox_witness + in + let inbox_hash = + Sc_rollup_proto_types.Inbox_hash.to_octez inbox_hash + in + let* () = + when_ (Node_context.dal_supported node_ctxt) @@ fun () -> + Dal_slots_tracker.process_head + node_ctxt + (Layer1.head_of_header head) + in + let* () = process_l1_block_operations node_ctxt head in + (* Avoid storing and publishing commitments if the head is not final. *) + (* Avoid triggering the pvm execution if this has been done before for + this head. *) + let* ctxt, _num_messages, num_ticks, initial_tick = + Interpreter.process_head + (module Rollup_node_plugin.Plugin) + node_ctxt + ctxt + ~predecessor + head + (inbox, messages) + in + let*! context_hash = Context.commit ctxt in + let* commitment_hash = + Publisher.process_head + (module Rollup_node_plugin.Plugin) + node_ctxt + ~predecessor:predecessor.hash + head + ctxt + in + let commitment_hash = + Option.map + Sc_rollup_proto_types.Commitment_hash.to_octez + commitment_hash + in + let* () = + unless (catching_up && Option.is_none commitment_hash) + @@ fun () -> Inbox.same_as_layer_1 node_ctxt head.hash inbox + in + let level = head.level in + let* previous_commitment_hash = + if level = node_ctxt.genesis_info.level then + (* Previous commitment for rollup genesis is itself. *) + return node_ctxt.genesis_info.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 = head.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* () = + Node_context.mark_finalized_level + node_ctxt + Int32.(sub head.level (of_int node_ctxt.block_finality_time)) + in + let* () = Node_context.save_l2_block node_ctxt l2_block in + return (`New l2_block)) + +and update_l2_chain daemon_components node_ctxt ~catching_up + (head : Layer1.header) = + let open Lwt_result_syntax in let start_timestamp = Time.System.now () in let* () = Node_context.save_level node_ctxt {Layer1.hash = head.hash; level = head.level} in - let* already_processed = Node_context.is_processed node_ctxt head.hash in - unless (already_processed || before_origination node_ctxt head) @@ fun () -> - let*! () = Daemon_event.head_processing head.hash head.level in - let* predecessor = Node_context.get_predecessor_header_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* () = exit_on_other_proto node_ctxt ~predecessor head in - let* () = - process_head daemon_components node_ctxt ~catching_up:true predecessor - in - let* ctxt = previous_context node_ctxt ~predecessor in - let* () = Node_context.save_protocol_info node_ctxt head ~predecessor in - let* inbox_hash, inbox, inbox_witness, messages = - Inbox.process_head node_ctxt ~predecessor head - in - let inbox_witness = - Sc_rollup_proto_types.Merkelized_payload_hashes_hash.to_octez - inbox_witness - in - let inbox_hash = Sc_rollup_proto_types.Inbox_hash.to_octez inbox_hash in - let* () = - when_ (Node_context.dal_supported node_ctxt) @@ fun () -> - Dal_slots_tracker.process_head node_ctxt (Layer1.head_of_header head) - in - let* () = process_l1_block_operations node_ctxt head in - (* Avoid storing and publishing commitments if the head is not final. *) - (* Avoid triggering the pvm execution if this has been done before for - this head. *) - let* ctxt, _num_messages, num_ticks, initial_tick = - Interpreter.process_head - (module Rollup_node_plugin.Plugin) - node_ctxt - ctxt - ~predecessor - head - (inbox, messages) - in - let*! context_hash = Context.commit ctxt in - let* commitment_hash = - Publisher.process_head - (module Rollup_node_plugin.Plugin) - node_ctxt - ~predecessor:predecessor.hash - head - ctxt - in - let commitment_hash = - Option.map - Sc_rollup_proto_types.Commitment_hash.to_octez - commitment_hash - in - let* () = - unless (catching_up && Option.is_none commitment_hash) @@ fun () -> - Inbox.same_as_layer_1 node_ctxt head.hash inbox - in - let level = head.level in - let* previous_commitment_hash = - if level = node_ctxt.genesis_info.level then - (* Previous commitment for rollup genesis is itself. *) - return node_ctxt.genesis_info.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 = head.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* () = - Node_context.mark_finalized_level - node_ctxt - Int32.(sub head.level (of_int node_ctxt.block_finality_time)) - in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* done_ = process_l1_block daemon_components node_ctxt ~catching_up head in + match done_ with + | `Nothing -> return_unit + | `Already_processed l2_block -> Node_context.set_l2_head node_ctxt l2_block + | `New l2_block -> + let* () = Node_context.set_l2_head node_ctxt l2_block in let stop_timestamp = Time.System.now () in let process_time = Ptime.diff stop_timestamp start_timestamp in + Metrics.Inbox.set_process_time process_time ; let*! () = Daemon_event.new_head_processed head.hash head.level process_time in - Metrics.Inbox.set_process_time process_time ; return_unit (* [on_layer_1_head node_ctxt head] processes a new head from the L1. It @@ -221,7 +253,7 @@ let on_layer_1_head (daemon_components : (module Daemon_components.S)) node_ctxt false trace -> (* The reorganization could not be computed entirely because of missing - info on the Layer 1. We fallback to a recursive process_head. *) + info on the Layer 1. We fallback to a recursive process_l1_block. *) Ok {Reorg.no_reorg with new_chain = [stripped_head]} | _ -> reorg in @@ -244,7 +276,7 @@ let on_layer_1_head (daemon_components : (module Daemon_components.S)) node_ctxt Layer1_helpers.prefetch_tezos_blocks node_ctxt.l1_ctxt to_prefetch ; let* header = get_header block in let catching_up = block.level < head.level in - process_head daemon_components node_ctxt ~catching_up header) + update_l2_chain daemon_components node_ctxt ~catching_up header) new_chain_prefetching in let* () = Publisher.publish_commitments () in @@ -423,7 +455,7 @@ let run node_ctxt configuration | e -> error_to_degraded_mode e) module Internal_for_tests = struct - (** Same as {!process_head} but only builds and stores the L2 block + (** Same as {!update_l2_chain} but only builds and stores the L2 block corresponding to [messages]. It is used by the unit tests to build an L2 chain. *) let process_messages (node_ctxt : _ Node_context.t) ~is_first_block @@ -490,7 +522,8 @@ module Internal_for_tests = struct let l2_block = Sc_rollup_block.{header; content = (); num_ticks; initial_tick} in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* () = Node_context.save_l2_block node_ctxt l2_block in + let* () = Node_context.set_l2_head node_ctxt l2_block in return l2_block end diff --git a/src/proto_018_Proxford/lib_sc_rollup_node/test/helpers/helpers.ml b/src/proto_018_Proxford/lib_sc_rollup_node/test/helpers/helpers.ml index 90604b57c821..425a37783aa7 100644 --- a/src/proto_018_Proxford/lib_sc_rollup_node/test/helpers/helpers.ml +++ b/src/proto_018_Proxford/lib_sc_rollup_node/test/helpers/helpers.ml @@ -114,7 +114,8 @@ let add_l2_genesis_block (node_ctxt : _ Node_context.t) ~boot_sector = initial_tick = Sc_rollup.Tick.to_z initial_tick; } in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* () = Node_context.save_l2_block node_ctxt l2_block in + let* () = Node_context.set_l2_head node_ctxt l2_block in return l2_block let initialize_node_context ?(constants = default_constants) kind ~boot_sector = -- GitLab From 4a71e50e46b0d99e59d66e7f5da934b10ca7608c Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 22 Aug 2023 08:59:24 +0200 Subject: [PATCH 4/7] SCORU/Node/Nairobi: fix setting head of reproposal --- .../lib_sc_rollup_node/daemon.ml | 202 ++++++++++-------- .../test/helpers/helpers.ml | 3 +- 2 files changed, 119 insertions(+), 86 deletions(-) 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 0d55f11c1f71..e518972e0d4a 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml @@ -80,97 +80,128 @@ let exit_after_proto_migration node_ctxt ?predecessor head = let*! _ = Lwt_exit.exit_and_wait 0 in return_unit -let rec process_head (daemon_components : (module Daemon_components.S)) +let rec process_l1_block (daemon_components : (module Daemon_components.S)) (node_ctxt : _ Node_context.t) ~catching_up (head : Layer1.header) = let open Lwt_result_syntax in + if before_origination node_ctxt head then return `Nothing + else + let* l2_head = Node_context.find_l2_block node_ctxt head.hash in + match l2_head with + | Some l2_head -> + (* Already processed *) + return (`Already_processed l2_head) + | None -> ( + (* New head *) + let*! () = Daemon_event.head_processing head.hash head.level in + let* predecessor = + Node_context.get_predecessor_header_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 `Nothing + | Some predecessor -> + let* () = exit_on_other_proto node_ctxt ~predecessor head in + let* () = + update_l2_chain + daemon_components + node_ctxt + ~catching_up:true + predecessor + in + let* ctxt = previous_context node_ctxt ~predecessor in + let* () = + Node_context.save_protocol_info node_ctxt head ~predecessor + in + let* inbox_hash, inbox, inbox_witness, messages = + Inbox.process_head node_ctxt ~predecessor head + in + let* () = + when_ (Node_context.dal_supported node_ctxt) @@ fun () -> + Dal_slots_tracker.process_head + node_ctxt + (Layer1.head_of_header head) + in + let* () = process_l1_block_operations node_ctxt head in + (* Avoid storing and publishing commitments if the head is not final. *) + (* Avoid triggering the pvm execution if this has been done before for + this head. *) + let* ctxt, _num_messages, num_ticks, initial_tick = + Interpreter.process_head + (module Rollup_node_plugin.Plugin) + node_ctxt + ctxt + ~predecessor + head + (inbox, messages) + in + let*! context_hash = Context.commit ctxt in + let* commitment_hash = + Publisher.process_head + (module Rollup_node_plugin.Plugin) + node_ctxt + ~predecessor:predecessor.hash + head + ctxt + in + let* () = + unless (catching_up && Option.is_none commitment_hash) + @@ fun () -> Inbox.same_as_layer_1 node_ctxt head.hash inbox + in + let level = head.level in + let* previous_commitment_hash = + if level = node_ctxt.genesis_info.level then + (* Previous commitment for rollup genesis is itself. *) + return node_ctxt.genesis_info.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 = head.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* () = + Node_context.mark_finalized_level + node_ctxt + Int32.(sub head.level (of_int node_ctxt.block_finality_time)) + in + let* () = Node_context.save_l2_block node_ctxt l2_block in + return (`New l2_block)) + +and update_l2_chain daemon_components node_ctxt ~catching_up + (head : Layer1.header) = + let open Lwt_result_syntax in let start_timestamp = Time.System.now () in let* () = Node_context.save_level node_ctxt {Layer1.hash = head.hash; level = head.level} in - let* already_processed = Node_context.is_processed node_ctxt head.hash in - unless (already_processed || before_origination node_ctxt head) @@ fun () -> - let*! () = Daemon_event.head_processing head.hash head.level in - let* predecessor = Node_context.get_predecessor_header_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* () = exit_on_other_proto node_ctxt ~predecessor head in - let* () = - process_head daemon_components node_ctxt ~catching_up:true predecessor - in - let* ctxt = previous_context node_ctxt ~predecessor in - let* () = Node_context.save_protocol_info node_ctxt head ~predecessor in - let* inbox_hash, inbox, inbox_witness, messages = - Inbox.process_head node_ctxt ~predecessor head - in - let* () = - when_ (Node_context.dal_supported node_ctxt) @@ fun () -> - Dal_slots_tracker.process_head node_ctxt (Layer1.head_of_header head) - in - let* () = process_l1_block_operations node_ctxt head in - (* Avoid storing and publishing commitments if the head is not final. *) - (* Avoid triggering the pvm execution if this has been done before for - this head. *) - let* ctxt, _num_messages, num_ticks, initial_tick = - Interpreter.process_head - (module Rollup_node_plugin.Plugin) - node_ctxt - ctxt - ~predecessor - head - (inbox, messages) - in - let*! context_hash = Context.commit ctxt in - let* commitment_hash = - Publisher.process_head - (module Rollup_node_plugin.Plugin) - node_ctxt - ~predecessor:predecessor.hash - head - ctxt - in - let* () = - unless (catching_up && Option.is_none commitment_hash) @@ fun () -> - Inbox.same_as_layer_1 node_ctxt head.hash inbox - in - let level = head.level in - let* previous_commitment_hash = - if level = node_ctxt.genesis_info.level then - (* Previous commitment for rollup genesis is itself. *) - return node_ctxt.genesis_info.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 = head.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* () = - Node_context.mark_finalized_level - node_ctxt - Int32.(sub head.level (of_int node_ctxt.block_finality_time)) - in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* done_ = process_l1_block daemon_components node_ctxt ~catching_up head in + match done_ with + | `Nothing -> return_unit + | `Already_processed l2_block -> Node_context.set_l2_head node_ctxt l2_block + | `New l2_block -> + let* () = Node_context.set_l2_head node_ctxt l2_block in let stop_timestamp = Time.System.now () in let process_time = Ptime.diff stop_timestamp start_timestamp in + Metrics.Inbox.set_process_time process_time ; let*! () = Daemon_event.new_head_processed head.hash head.level process_time in @@ -210,7 +241,7 @@ let on_layer_1_head (daemon_components : (module Daemon_components.S)) node_ctxt false trace -> (* The reorganization could not be computed entirely because of missing - info on the Layer 1. We fallback to a recursive process_head. *) + info on the Layer 1. We fallback to a recursive process_l1_block. *) Ok {Reorg.no_reorg with new_chain = [stripped_head]} | _ -> reorg in @@ -233,7 +264,7 @@ let on_layer_1_head (daemon_components : (module Daemon_components.S)) node_ctxt Layer1_helpers.prefetch_tezos_blocks node_ctxt.l1_ctxt to_prefetch ; let* header = get_header block in let catching_up = block.level < head.level in - process_head daemon_components node_ctxt ~catching_up header) + update_l2_chain daemon_components node_ctxt ~catching_up header) new_chain_prefetching in let* () = Publisher.publish_commitments () in @@ -411,7 +442,7 @@ let run node_ctxt configuration | e -> error_to_degraded_mode e) module Internal_for_tests = struct - (** Same as {!process_head} but only builds and stores the L2 block + (** Same as {!update_l2_chain} but only builds and stores the L2 block corresponding to [messages]. It is used by the unit tests to build an L2 chain. *) let process_messages (node_ctxt : _ Node_context.t) ~is_first_block @@ -470,7 +501,8 @@ module Internal_for_tests = struct let l2_block = Sc_rollup_block.{header; content = (); num_ticks; initial_tick} in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* () = Node_context.save_l2_block node_ctxt l2_block in + let* () = Node_context.set_l2_head node_ctxt l2_block in return l2_block end diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/test/helpers/helpers.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/test/helpers/helpers.ml index ceed3e3bcc55..cd7ba97147be 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/test/helpers/helpers.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/test/helpers/helpers.ml @@ -117,7 +117,8 @@ let add_l2_genesis_block (node_ctxt : _ Node_context.t) ~boot_sector = initial_tick = Sc_rollup.Tick.to_z initial_tick; } in - let* () = Node_context.save_l2_head node_ctxt l2_block in + let* () = Node_context.save_l2_block node_ctxt l2_block in + let* () = Node_context.set_l2_head node_ctxt l2_block in return l2_block let initialize_node_context ?(constants = default_constants) kind ~boot_sector = -- GitLab From 869d146b77bd1fc82c9b07c2b4f0f0d133d9e39c Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 21 Aug 2023 20:11:17 +0200 Subject: [PATCH 5/7] Tezt/Tezos: update SCORU node level on more events --- tezt/lib_tezos/sc_rollup_node.ml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tezt/lib_tezos/sc_rollup_node.ml b/tezt/lib_tezos/sc_rollup_node.ml index b309945e3d5a..fc41602670ee 100644 --- a/tezt/lib_tezos/sc_rollup_node.ml +++ b/tezt/lib_tezos/sc_rollup_node.ml @@ -376,6 +376,9 @@ let handle_event sc_node {name; value; timestamp = _} = | "sc_rollup_node_layer_1_new_head_processed.v0" -> let level = JSON.(value |-> "level" |> as_int) in update_level sc_node level + | "sc_rollup_node_layer_1_new_heads_processed.v0" -> + let level = JSON.(value |-> "to" |> as_int) in + update_level sc_node level | _ -> () let create_with_endpoint ?runner ?path ?name ?color ?data_dir ~base_dir -- GitLab From 38252009374979a465abd588a337a0b7ee70a3e2 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 21 Aug 2023 18:43:48 +0200 Subject: [PATCH 6/7] Test: ensure correct commitment published in reproposal reorgs --- tezt/tests/sc_rollup.ml | 114 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 6e5a03c19ae0..0ab4e641889c 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -2228,6 +2228,113 @@ let commitments_reorgs ~switch_l1_node ~kind _protocol sc_rollup_node (Option.map commitment_info_commitment published_commitment, "published") ; check_published_commitment_in_l1 sc_rollup client published_commitment +(* This test simulate a reorganisation where a block is reproposed, and ensures + that the correct commitment is published. *) +let commitments_reproposal _protocol sc_rollup_node sc_rollup_client sc_rollup + node1 client1 = + let* genesis_info = + RPC.Client.call ~hooks client1 + @@ RPC.get_chain_block_context_smart_rollups_smart_rollup_genesis_info + sc_rollup + in + let init_level = JSON.(genesis_info |-> "level" |> as_int) in + let* levels_to_commitment = + get_sc_rollup_commitment_period_in_blocks client1 + in + let nodes_args = + Node.[Synchronisation_threshold 0; History_mode Archive; No_bootstrap_peers] + in + let* node2, client2 = Client.init_with_node ~nodes_args `Client () in + let* () = Client.Admin.trust_address client1 ~peer:node2 + and* () = Client.Admin.trust_address client2 ~peer:node1 in + let* () = Client.Admin.connect_address client1 ~peer:node2 in + let* () = + Sc_rollup_node.run sc_rollup_node ~event_level:`Debug sc_rollup [] + in + (* We bake `sc_rollup_commitment_period_in_blocks - 1` levels, which + should cause both nodes to observe level + `sc_rollup_commitment_period_in_blocks + init_level - 1 . *) + let* () = bake_levels (levels_to_commitment - 1) client1 in + let* _ = Node.wait_for_level node2 (init_level + levels_to_commitment - 1) in + let* _ = Sc_rollup_node.wait_sync ~timeout:3. sc_rollup_node in + Log.info "Nodes are synchronized." ; + Log.info "Forking." ; + let* identity2 = Node.wait_for_identity node2 in + let* () = Client.Admin.kick_peer client1 ~peer:identity2 in + let* () = send_text_messages client1 ["message1"] + and* () = send_text_messages client2 ["message2"] in + let* header1 = RPC.Client.call client1 @@ RPC.get_chain_block_header () + and* header2 = RPC.Client.call client2 @@ RPC.get_chain_block_header () in + let level1 = JSON.(header1 |-> "level" |> as_int) in + let level2 = JSON.(header2 |-> "level" |> as_int) in + let hash1 = JSON.(header1 |-> "hash" |> as_string) in + let hash2 = JSON.(header2 |-> "hash" |> as_string) in + Check.((level1 = level2) int) + ~error_msg:"Heads levels should be identical but %L <> %R" ; + Check.((JSON.encode header1 <> JSON.encode header2) string) + ~error_msg:"Heads should be distinct: %L and %R" ; + Log.info "Nodes are following distinct branches." ; + let* _ = Sc_rollup_node.wait_sync ~timeout:10. sc_rollup_node in + let check_sc_head hash = + let*! sc_head = + Sc_rollup_client.rpc_get + ~hooks + sc_rollup_client + ["global"; "block"; "head"; "hash"] + in + let sc_head = JSON.as_string sc_head in + Check.((sc_head = hash) string) + ~error_msg:"Head of rollup node %L should be one of node %R" ; + unit + in + let* () = check_sc_head hash1 in + let*! state_hash1 = + Sc_rollup_client.state_hash sc_rollup_client ~block:hash1 + in + Log.info "Changing L1 node for rollup node (1st reorg)" ; + let* () = + Sc_rollup_node.change_node_and_restart + ~event_level:`Debug + sc_rollup_node + sc_rollup + node2 + in + let* _ = Sc_rollup_node.wait_sync ~timeout:10. sc_rollup_node in + let* () = check_sc_head hash2 in + let*! state_hash2 = + Sc_rollup_client.state_hash sc_rollup_client ~block:hash2 + in + Log.info + "Changing L1 node for rollup node (2nd reorg), back to first node to \ + simulate reproposal of round 0" ; + let* () = + Sc_rollup_node.change_node_and_restart + ~event_level:`Debug + sc_rollup_node + sc_rollup + node1 + in + let* _ = Sc_rollup_node.wait_sync ~timeout:10. sc_rollup_node in + let* () = check_sc_head hash1 in + Check.((state_hash1 <> state_hash2) string) + ~error_msg:"States should be distinct" ; + let state_hash_to_commit = state_hash1 in + (* exactly one level left to finalize the commitment in the node. *) + let* () = bake_levels (block_finality_time + 2) client1 in + let* commitment = + RPC.Client.call client1 + @@ RPC + .get_chain_block_context_smart_rollups_smart_rollup_staker_staked_on_commitment + ~sc_rollup + Constant.bootstrap1.public_key_hash + in + let commitment_state_hash = + JSON.(commitment |-> "compressed_state" |> as_string) + in + Check.((commitment_state_hash = state_hash_to_commit) string) + ~error_msg:"Safety error: committed state %L instead of state %R" ; + unit + type balances = {liquid : int; frozen : int} let contract_balances ~pkh client = @@ -6182,6 +6289,13 @@ let register ~kind ~protocols = (commitments_reorgs ~kind ~switch_l1_node:true) protocols ~kind ; + test_commitment_scenario + ~commitment_period:3 + ~variant:"correct_commitment_in_reproposal_reorg" + ~extra_tags:["reproposal"] + commitments_reproposal + protocols + ~kind ; test_commitment_scenario ~challenge_window:1 ~variant:"no_commitment_publish_before_lcc" -- GitLab From 613777fd5c8fae7b494d16808fb39fc11186f6d9 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Wed, 23 Aug 2023 09:57:40 +0200 Subject: [PATCH 7/7] Doc: changelog for head fix --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fb07278c8f7d..f853c9e2b52a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -97,6 +97,9 @@ Smart Rollup node kernel logs should be written (this file is in ``/simulation_kernel_logs``). (MR :gl:`!9606`) +- Fixed an issue where the rollup node could forget to update its L2 head for a + block. (MR :gl:`!9868`) + Smart Rollup client ------------------- -- GitLab