From 01260e7f4219d0dc76848623213281b48f2c0616 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Thu, 1 Sep 2022 16:50:58 +0200 Subject: [PATCH 1/4] Proto: expose Dal.Endorsement.is_available in Alpha_context --- src/proto_alpha/lib_protocol/alpha_context.mli | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 0a14bfd1c481..88985286259c 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2821,6 +2821,8 @@ module Dal : sig val empty : t + val is_available : t -> Slot_index.t -> bool + val occupied_size_in_bits : t -> int val expected_size_in_bits : max_index:Slot_index.t -> int -- GitLab From 0066ed8b61567913a208e84772f6517599d413b3 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Thu, 1 Sep 2022 16:51:40 +0200 Subject: [PATCH 2/4] Dal/Rollup node: download block receipt and check slot availability --- src/proto_alpha/bin_sc_rollup_node/daemon.ml | 10 +- .../bin_sc_rollup_node/dal_slots_tracker.ml | 146 +++++++++++++- .../bin_sc_rollup_node/dal_slots_tracker.mli | 13 +- .../dal_slots_tracker_event.ml | 50 +++++ src/proto_alpha/bin_sc_rollup_node/store.ml | 179 ++++++++++++++---- 5 files changed, 354 insertions(+), 44 deletions(-) create mode 100644 src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker_event.ml diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index e01d160e312b..75f67a7b04a2 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -187,17 +187,11 @@ module Make (PVM : Pvm.S) = struct else return_unit else let* ctxt = Inbox.process_head node_ctxt head in + let* () = Dal_slots_tracker.process_head node_ctxt head in let* () = process_l1_block_operations ~finalized 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* () = Components.Interpreter.process_head node_ctxt ctxt head in - - (* DAL/FIXME: https://gitlab.com/tezos/tezos/-/issues/3166 - - If the rollup is subscribed to at least one slot, then the inbox for - this block will be downloaded after lag levels have passed and the - dal slots have been declared available. *) - Dal_slots_tracker.process_head node_ctxt head + Components.Interpreter.process_head node_ctxt ctxt head in let* () = when_ finalized @@ fun () -> diff --git a/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker.ml b/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker.ml index 69c0fc8374c5..9d828cb2c43a 100644 --- a/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker.ml +++ b/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker.ml @@ -27,6 +27,24 @@ open Protocol open Alpha_context module Block_services = Block_services.Make (Protocol) (Protocol) +type error += Cannot_read_block_metadata of Block_hash.t + +let () = + register_error_kind + ~id:"sc_rollup.node.cannot_read_receipt_of_block" + ~title:"Cannot read receipt of block from L1" + ~description:"The receipt of a block could not be read." + ~pp:(fun ppf hash -> + Format.fprintf + ppf + "Could not read block receipt for block with hash %a." + Block_hash.pp + hash) + `Temporary + Data_encoding.(obj1 (req "hash" Block_hash.encoding)) + (function Cannot_read_block_metadata hash -> Some hash | _ -> None) + (fun hash -> Cannot_read_block_metadata hash) + let get_slot_subscriptions cctxt head rollup = let open Lwt_result_syntax in let*? level = Environment.wrap_tzresult @@ Raw_level.of_int32 (snd head) in @@ -36,11 +54,137 @@ let get_slot_subscriptions cctxt head rollup = rollup level -let process_head Node_context.{cctxt; rollup_address; store; _} +(* [fetch_and_save_subscribed_slot_headers cctxt head rollup] fetches the slot + indices to which [rollup] is subscribed to at [head], and stores them. *) +let fetch_and_save_subscribed_slot_headers + Node_context.{cctxt; rollup_address; store; _} Layer1.(Head {level; hash = head_hash}) = let open Lwt_result_syntax in let* res = get_slot_subscriptions cctxt (head_hash, level) rollup_address in let*! () = Store.Dal_slot_subscriptions.add store head_hash res in return_unit +let rec predecessor_hash ~levels node_ctxt (Layer1.Head {hash; level} as head) = + let open Lwt_option_syntax in + let genesis_level = node_ctxt.Node_context.genesis_info.level in + if level < Raw_level.to_int32 genesis_level then fail + else if levels = 0 then return hash + else + let*! pred_hash = Layer1.predecessor node_ctxt.store head in + let pred_head = + Layer1.Head {hash = pred_hash; level = Int32.(pred level)} + in + predecessor_hash ~levels:(levels - 1) node_ctxt pred_head + +(* Intersect two sorted lists. *) +module Slot_set = Set.Make (Dal.Slot_index) + +let common_slot_indexes (l : Dal.Slot_index.t list) (r : Dal.Slot_index.t list) + = + let l_set = Slot_set.of_list l in + let r_set = Slot_set.of_list r in + Slot_set.elements @@ Slot_set.inter l_set r_set + +(* [confirmed_slots node_ctxt head] reads the slot indexes that have been + declared available from [head]'s block receipt, and returns the list of + confirmed slots to which the rollup is subscribed to, with the corresponding + slot header. *) +let confirmed_slots node_ctxt (Layer1.Head {hash; _} as head) = + (* DAL/FIXME: https://gitlab.com/tezos/tezos/-/issues/3722 + The case for protocol migrations when the lag constant has + been changed is tricky, especially if the lag is reduced. + Suppose that a slot header is published at the second last level of a + cycle, and the lag is 2. The block is expected to be confirmed at the + first level of the new cycle. However, if during the protocol migration + we reduce the lag to 1, then the slots header will never be confirmed. + *) + let open Lwt_result_syntax in + let lag = + node_ctxt.Node_context.protocol_constants.parametric.dal.endorsement_lag + in + (* we are downloading endorsemented for slots at level [level], so + we need to download the data at level [level - lag]. + Therefore, we need to check only the slots to which the rollup node + was subscribed to at level `level - lag` + *) + let*! published_slots_block_hash = + predecessor_hash ~levels:lag node_ctxt head + in + match published_slots_block_hash with + | None -> return [] + | Some published_block_hash -> + let* {metadata; _} = + Layer1.fetch_tezos_block node_ctxt.Node_context.l1_ctxt hash + in + let*? metadata = + Option.to_result + ~none:(TzTrace.make @@ Cannot_read_block_metadata hash) + metadata + in + (* `metadata.protocol_data.dal_slot_availability` is `None` if we are behind + the `Dal feature flag`: in this case we return an empty slot endorsement. + *) + let confirmed_slots = + Option.value + ~default:Dal.Endorsement.empty + metadata.protocol_data.dal_slot_availability + in + let*! subscribed_slots_indexes = + Store.Dal_slot_subscriptions.get node_ctxt.store published_block_hash + in + let*! published_slots_indexes = + Store.Dal_slots.list_secondary_keys + node_ctxt.store + ~primary_key:published_block_hash + in + (* The list of slot_indexes l and r are guaranteed to be sorted. + List l is sorted because the protocol computes it from a bitset, + scanning the values 1 by 1. + List r is sorted because of the internal logic of list_inner_keys. *) + let relevant_slots_indexes = + common_slot_indexes subscribed_slots_indexes published_slots_indexes + |> List.filter (Dal.Endorsement.is_available confirmed_slots) + in + let*! () = + List.iter_s + (fun s -> + Dal_slots_tracker_event.slot_has_been_confirmed + s + published_block_hash + hash) + relevant_slots_indexes + in + let*! confirmed_slot_indexes_with_header = + List.map_p + (fun slot_index -> + Store.Dal_slots.get + node_ctxt.store + ~primary_key:published_block_hash + ~secondary_key:slot_index) + relevant_slots_indexes + in + + return confirmed_slot_indexes_with_header + +let compute_and_save_confirmed_slot_headers node_ctxt + (Layer1.Head {hash; _} as head) = + let open Lwt_result_syntax in + let* slots_to_save = confirmed_slots node_ctxt head in + let*! () = + List.iter_s + (fun ({Dal.Slot.index; _} as slot) -> + Store.Dal_confirmed_slots.add + node_ctxt.store + ~primary_key:hash + ~secondary_key:index + slot) + slots_to_save + in + return_unit + +let process_head node_ctxt head = + let open Lwt_result_syntax in + let* () = fetch_and_save_subscribed_slot_headers node_ctxt head in + compute_and_save_confirmed_slot_headers node_ctxt head + let start () = Lwt.return () diff --git a/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker.mli b/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker.mli index 53a71bb09a3e..78cae8869288 100644 --- a/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker.mli +++ b/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker.mli @@ -32,8 +32,17 @@ The state of subscribed slots per block is persistent. *) -(** [process_head node_ctxt head] fetches the slot indices to which the rollup - is subscribed to, and stores them. *) +type error += Cannot_read_block_metadata of Block_hash.t + +(** [process_head node_ctxt head] performs the following operations: + {ul + {li it fetches the slot indices to which the rollup is subscribed to, + and stores them in [Store.Dal_slot_subbscriptions] } + {li it reads the endorsements for headers published endorsement_lag + levels preceding [head] from the block metadata, determines which + ones the rollup node will download, and stores the results in + [Store.Dal_confirmed_slots].} + } *) val process_head : Node_context.t -> Layer1.head -> unit tzresult Lwt.t (** [start ()] initializes the Dal_slot_tracker to track the dal slot subscriptions. *) diff --git a/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker_event.ml b/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker_event.ml new file mode 100644 index 000000000000..c06c8afd1309 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/dal_slots_tracker_event.ml @@ -0,0 +1,50 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 TriliTech *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context + +module Simple = struct + include Internal_event.Simple + + let section = ["sc_rollup_node"; "dal_slots_tracker"] + + let slot_has_been_confirmed = + declare_3 + ~section + ~name:"dal_confirmed_slot" + ~msg: + "Slot header for index {slot_index} was published at block \ + {published_hash}. The rollup was subscribed to the slot index and the \ + slot header has been confirmed at {confirmed_hash}. The slot contents \ + will be downloaded." + ~level:Notice + ("slot_index", Dal.Slot_index.encoding) + ("published_hash", Block_hash.encoding) + ("confirmed_hash", Block_hash.encoding) +end + +let slot_has_been_confirmed slot published_hash confirmed_hash = + Simple.(emit slot_has_been_confirmed (slot, published_hash, confirmed_hash)) diff --git a/src/proto_alpha/bin_sc_rollup_node/store.ml b/src/proto_alpha/bin_sc_rollup_node/store.ml index 634f5adc92fd..2aab02f4ac21 100644 --- a/src/proto_alpha/bin_sc_rollup_node/store.ml +++ b/src/proto_alpha/bin_sc_rollup_node/store.ml @@ -424,27 +424,52 @@ module Contexts = Make_append_only_map (struct let value_encoding = Context.hash_encoding end) -(* Published slot headers per block hash, - stored as a list of bindings from `Dal_slot_index.t` - to `Dal.Slot.t`. The encoding function converts this - list into a `Dal.Slot_index.t`-indexed map. *) -module Dal_slots = struct - module Dal_slots_map = Map.Make (struct - type t = Dal.Slot_index.t +(* A nested map is a (IStore) updatable map whose values are themselves + maps constructed via the `Map.Make` functor, encoded in the Irmin + store as lists of bindings. The module returned by the + `Make_nested_map` functor implement some utility functions on top of + Make_nested_map, to provide functionalities for iterating over the + bindings of the store value. + It also shadows the get, add and mem operations of updatable_maps, + to allow updating and retrieveing only one binding from the value of the + store. The keys of the IStore updatable map are referred to + as primary keys. The keys of the values of the updatable + map are referred to as secondary keys. +*) +module Make_nested_map (K : sig + type key - let compare = Dal.Slot_index.compare - end) + val string_of_key : key -> string - include Make_updatable_map (struct - let keep_last_n_entries_in_memory = 10 + val path : string list + + val keep_last_n_entries_in_memory : int - let path = ["dal"; "slot_headers"] + type secondary_key - type key = Block_hash.t + val secondary_key_name : string - let string_of_key = Block_hash.to_b58check + val value_name : string - type value = Dal.Slot.t Dal_slots_map.t + val compare_secondary_keys : secondary_key -> secondary_key -> int + + type value + + val secondary_key_encoding : secondary_key Data_encoding.t + + val value_encoding : value Data_encoding.t +end) = +struct + module Secondary_key_map = Map.Make (struct + type t = K.secondary_key + + let compare = K.compare_secondary_keys + end) + + include Make_updatable_map (struct + include K + + type value = K.value Secondary_key_map.t (* DAL/FIXME: https://gitlab.com/tezos/tezos/-/issues/3732 Use data encodings for maps once they are available in the @@ -452,56 +477,144 @@ module Dal_slots = struct *) let value_encoding = Data_encoding.conv - (fun dal_slots_map -> Dal_slots_map.bindings dal_slots_map) + (fun dal_slots_map -> Secondary_key_map.bindings dal_slots_map) (fun dal_slots_bindings -> - Dal_slots_map.of_seq @@ List.to_seq dal_slots_bindings) + Secondary_key_map.of_seq @@ List.to_seq dal_slots_bindings) Data_encoding.( list @@ obj2 - (req "slot_index" Dal.Slot_index.encoding) - (req "slot_metadata" Dal.Slot.encoding)) + (req K.secondary_key_name K.secondary_key_encoding) + (req K.value_name K.value_encoding)) end) + (* Return the bindings associated with a primary key. *) let list_secondary_keys_with_values store ~primary_key = let open Lwt_syntax in let+ slots_map = find store primary_key in - Option.fold ~none:[] ~some:Dal_slots_map.bindings slots_map + Option.fold ~none:[] ~some:Secondary_key_map.bindings slots_map + + (* Check whether the updatable store contains an entry + for the primary_key, which itself contains a + binding whose key is ~secondary_key. *) + let mem store ~primary_key ~secondary_key = + let open Lwt_syntax in + let* primary_key_binding_exists = mem store primary_key in + if not primary_key_binding_exists then return false + else + let+ secondary_key_map = get store primary_key in + Secondary_key_map.mem secondary_key secondary_key_map + (* unsafe call. The existence of a value for + primary and secondary key in the store must be + checked before invoking this function. *) + let get store ~primary_key ~secondary_key = + let open Lwt_syntax in + let+ secondary_key_map = get store primary_key in + match Secondary_key_map.find secondary_key secondary_key_map with + | None -> + (* value for primary and secondary key does not exist *) + assert false + | Some value -> value + + (* Returns the set of keys from the bindings of + ~primary_key in the store. *) let list_secondary_keys store ~primary_key = let open Lwt_syntax in let+ secondary_keys_with_values = list_secondary_keys_with_values store ~primary_key in - secondary_keys_with_values |> List.map fst + List.map (fun (key, _value) -> key) secondary_keys_with_values + (* Returns the set of values from the bindings of + ~primary_key in the store. *) let list_values store ~primary_key = let open Lwt_syntax in let+ secondary_keys_with_values = list_secondary_keys_with_values store ~primary_key in - secondary_keys_with_values |> List.map snd + List.map (fun (_key, value) -> value) secondary_keys_with_values + (* Updates the entry of the updatable map with key ~primary_key + by adding to it a binding with key ~secondary_key and + value `value`.*) let add store ~primary_key ~secondary_key value = let open Lwt_syntax in - let* slots_map = find store primary_key in - let slots_map = Option.value ~default:Dal_slots_map.empty slots_map in + let* value_map = find store primary_key in + let value_map = Option.value ~default:Secondary_key_map.empty value_map in let value_can_be_added = - match Dal_slots_map.find secondary_key slots_map with + match Secondary_key_map.find secondary_key value_map with | None -> true | Some old_value -> Data_encoding.Binary.( Bytes.equal - (to_bytes_exn Dal.Slot.encoding old_value) - (to_bytes_exn Dal.Slot.encoding value)) + (to_bytes_exn K.value_encoding old_value) + (to_bytes_exn K.value_encoding value)) in if value_can_be_added then - let new_slots_map = Dal_slots_map.add secondary_key value slots_map in - add store primary_key new_slots_map + let updated_map = Secondary_key_map.add secondary_key value value_map in + add store primary_key updated_map else Stdlib.failwith (Printf.sprintf - "A binding for slot index %d under block_hash %s already exists \ - with a different value" - (Dal.Slot_index.to_int secondary_key) - (Block_hash.to_b58check primary_key)) + "A binding for binding %s under block_hash %s already exists with a \ + different %s" + (Data_encoding.Binary.to_string_exn + K.secondary_key_encoding + secondary_key) + (K.string_of_key primary_key) + K.value_name) +end + +module Block_slot_map_parameter = struct + type key = Block_hash.t + + let string_of_key = Block_hash.to_b58check + + type secondary_key = Dal.Slot_index.t + + let compare_secondary_keys = Dal.Slot_index.compare + + type value = Dal.Slot.t + + let secondary_key_encoding = Dal.Slot_index.encoding + + let secondary_key_name = "slot_index" + + let value_encoding = Dal.Slot.encoding + + let value_name = "slots_metadata" end + +(* Published slot headers per block hash, + stored as a list of bindings from `Dal_slot_index.t` + to `Dal.Slot.t`. The encoding function converts this + list into a `Dal.Slot_index.t`-indexed map. *) +module Dal_slots = Make_nested_map (struct + let path = ["dal"; "slot_headers"] + + (* FIXME/DAL: https://gitlab.com/tezos/tezos/-/issues/3527 + This value is currently not used, but required by the module type. *) + let keep_last_n_entries_in_memory = 10 + + include Block_slot_map_parameter +end) + +(* DAL/FIXME: https://gitlab.com/tezos/tezos/-/issues/3515 + temporary - this is used for test purposes only. + Remove this piece of storage once we download blocks from the dal node. +*) +(* Published slot headers per block hash, stored as a list of bindings from + `Dal_slot_index.t` to `Dal.Slot.t`. The encoding function converts this + list into a `Dal.Slot_index.t`-indexed map. Note that the block_hash + refers to the block where slots headers have been confirmed, not + the block where they have been published. +*) +module Dal_confirmed_slots = Make_nested_map (struct + let path = ["dal"; "confirmed_slots"] + + (* FIXME/DAL: https://gitlab.com/tezos/tezos/-/issues/3527 + This value is currently not used, but required by the module type. *) + let keep_last_n_entries_in_memory = 10 + + include Block_slot_map_parameter +end) -- GitLab From 6be43f9759f241c06fd67e316d278e48ba829d78 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Thu, 4 Aug 2022 21:16:14 +0100 Subject: [PATCH 3/4] Dal/Rollup node: endpoint for fetching endorsed slots for rollup --- src/proto_alpha/bin_sc_rollup_node/RPC_server.ml | 13 +++++++++++++ src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml index b707417b2520..56c9e153945c 100644 --- a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml +++ b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml @@ -52,6 +52,12 @@ let get_dal_slots store = let*! slot_headers = Store.Dal_slots.list_values store ~primary_key:head in return slot_headers +let get_dal_confirmed_slots store = + let open Lwt_result_syntax in + let* head = get_head store in + let*! l = Store.Dal_confirmed_slots.list_values store ~primary_key:head in + return l + let commitment_with_hash commitment = ( Protocol.Alpha_context.Sc_rollup.Commitment.hash_uncarbonated commitment, commitment ) @@ -242,6 +248,12 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct (Sc_rollup_services.Global.dal_slots ()) (fun () () -> get_dal_slots store) + let register_dal_confirmed_slots store dir = + RPC_directory.register0 + dir + (Sc_rollup_services.Global.dal_confirmed_slots ()) + (fun () () -> get_dal_confirmed_slots store) + let register (node_ctxt : Node_context.t) configuration = RPC_directory.empty |> register_sc_rollup_address configuration @@ -257,6 +269,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct |> register_last_published_commitment node_ctxt.store |> register_dal_slot_subscriptions node_ctxt.store |> register_dal_slots node_ctxt.store + |> register_dal_confirmed_slots node_ctxt.store let start node_ctxt configuration = Common.start configuration (register node_ctxt configuration) diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml index 8a2e64debb96..6f0a82d99f4b 100644 --- a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml @@ -144,6 +144,13 @@ module Global = struct ~query:RPC_query.empty ~output:(Data_encoding.list Dal.Slot.encoding) (prefix / "dal" / "slots") + + let dal_confirmed_slots () = + RPC_service.get_service + ~description:"Data availability confirmed slots for a given block hash" + ~query:RPC_query.empty + ~output:(Data_encoding.list Dal.Slot.encoding) + (prefix / "dal" / "confirmed_slots") end module Local = struct -- GitLab From 16a71b6b83831165306a2f4a7a2c5a49308637ce Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Tue, 9 Aug 2022 16:43:25 +0100 Subject: [PATCH 4/4] Dal/Tezt: tweak test to check for tracking of available slots --- tezt/lib_tezos/sc_rollup_client.ml | 12 +++ tezt/lib_tezos/sc_rollup_client.mli | 5 ++ tezt/tests/dal.ml | 73 +++++++++++++++++-- ...ctionality (rollup_node_dal_headers_s.out | 12 ++- 4 files changed, 93 insertions(+), 9 deletions(-) diff --git a/tezt/lib_tezos/sc_rollup_client.ml b/tezt/lib_tezos/sc_rollup_client.ml index 9bbc867c7311..ac6078c225f2 100644 --- a/tezt/lib_tezos/sc_rollup_client.ml +++ b/tezt/lib_tezos/sc_rollup_client.ml @@ -164,6 +164,18 @@ let dal_slots_metadata ?hooks sc_client = index = obj |> get "index" |> as_int; })) +let dal_confirmed_slots_metadata ?hooks sc_client = + let open Lwt.Syntax in + let+ json = rpc_get ?hooks sc_client ["global"; "dal"; "confirmed_slots"] in + JSON.( + as_list json + |> List.map (fun obj -> + { + level = obj |> get "level" |> as_int; + header = obj |> get "header" |> as_string; + index = obj |> get "index" |> as_int; + })) + let spawn_generate_keys ?hooks ?(force = false) ~alias sc_client = spawn_command ?hooks diff --git a/tezt/lib_tezos/sc_rollup_client.mli b/tezt/lib_tezos/sc_rollup_client.mli index e6476896eccb..535d5292fc43 100644 --- a/tezt/lib_tezos/sc_rollup_client.mli +++ b/tezt/lib_tezos/sc_rollup_client.mli @@ -96,6 +96,11 @@ val dal_slot_subscriptions : ?hooks:Process.hooks -> t -> int list Lwt.t head seen by the rollup node. *) val dal_slots_metadata : ?hooks:Process.hooks -> t -> slot_metadata list Lwt.t +(** [dal_confirmed_slots_metadata client] returns the dal confirmed slot + metadata of the last tezos head seen by the rollup node. *) +val dal_confirmed_slots_metadata : + ?hooks:Process.hooks -> t -> slot_metadata list Lwt.t + (** [generate_keys ~alias client] generates new unencrypted keys for [alias]. *) val generate_keys : ?hooks:Process.hooks -> ?force:bool -> alias:string -> t -> unit Lwt.t diff --git a/tezt/tests/dal.ml b/tezt/tests/dal.ml index 1d6d97b74689..8a1d86495b41 100644 --- a/tezt/tests/dal.ml +++ b/tezt/tests/dal.ml @@ -513,13 +513,19 @@ let test_dal_node_startup = let rollup_node_stores_dal_slots _protocol sc_rollup_node sc_rollup_address node client = - (* Check that the rollup node stores the slots published in a block, along with slot headers: + (* Check that the rollup node stores the slots published in a block, + along with slot headers: 1. Run rollup node for an originated rollup - 2. Execute a client command to publish a slot header, bake one level - 3. Repeat step two for a second slot header - 4. Get the list of slot headers for the rollup node - 5. Determine that the list contains the two slot headers published. + 2. Subscribe rollup node to slot 0, bake one level + 3. Subscribe rollup node to slot 1, bake one level + 4. Execute a client command to publish a slot header for slot 0, + bake one level + 5. Execute a client command to publish a slot header for slot 1, + bake one level + 4. Get the list of slot headers subscribed and confirmed for the + rollup node + 5. Determine that the list contains only the header for slot 1. *) let* parameters = Rollup.Dal.Parameters.from_client client in let cryptobox = Rollup.Dal.make parameters in @@ -534,6 +540,18 @@ let rollup_node_stores_dal_slots _protocol sc_rollup_node sc_rollup_address node Check.(level = init_level) Check.int ~error_msg:"Current level has moved past origination level (%L = %R)" ; + let* (`OpHash _) = + subscribe_to_dal_slot client ~sc_rollup_address ~slot_index:0 + in + let* first_subscription_level = + Sc_rollup_node.wait_for_level sc_rollup_node (init_level + 1) + in + let* (`OpHash _) = + subscribe_to_dal_slot client ~sc_rollup_address ~slot_index:1 + in + let* second_subscription_level = + Sc_rollup_node.wait_for_level sc_rollup_node (first_subscription_level + 1) + in let* _ = publish_slot ~source:Constant.bootstrap1 @@ -556,8 +574,21 @@ let rollup_node_stores_dal_slots _protocol sc_rollup_node sc_rollup_address node node client in + let* _ = + publish_slot + ~source:Constant.bootstrap3 + ~fee:1_200 + ~index:2 + ~message:"C0FFEE" + parameters + cryptobox + node + client + in let* () = Client.bake_for_and_wait client in - let* _level = Sc_rollup_node.wait_for_level sc_rollup_node (init_level + 1) in + let* slots_published_level = + Sc_rollup_node.wait_for_level sc_rollup_node (second_subscription_level + 1) + in let* slots_metadata = Sc_rollup_client.dal_slots_metadata ~hooks sc_rollup_client in @@ -567,11 +598,39 @@ let rollup_node_stores_dal_slots _protocol sc_rollup_node sc_rollup_address node (fun msg -> Tezos_crypto_dal.Cryptobox.Commitment.to_b58check @@ Rollup.Dal.Commitment.dummy_commitment parameters cryptobox msg) - ["CAFEBABE"; "CAFEDEAD"] + ["CAFEBABE"; "CAFEDEAD"; "C0FFEE"] in Check.(slot_headers = expected_slot_headers) (Check.list Check.string) ~error_msg:"Unexpected list of slot headers (%L = %R)" ; + (* Endorse only slots 1 and 2. *) + let* _ = slot_availability ~signer:Constant.bootstrap1 [2; 1; 0] client in + let* _ = slot_availability ~signer:Constant.bootstrap2 [2; 1; 0] client in + let* _ = slot_availability ~signer:Constant.bootstrap3 [2; 1] client in + let* _ = slot_availability ~signer:Constant.bootstrap4 [2; 1] client in + let* _ = slot_availability ~signer:Constant.bootstrap5 [2; 1] client in + let* () = Client.bake_for_and_wait client in + let* level = + Sc_rollup_node.wait_for_level sc_rollup_node (slots_published_level + 1) + in + Check.(level = slots_published_level + 1) + Check.int + ~error_msg:"Current level has moved past slot endorsement level (%L = %R)" ; + let* confirmed_slots_metadata = + Sc_rollup_client.dal_confirmed_slots_metadata ~hooks sc_rollup_client + in + let confirmed_slot_headers = + confirmed_slots_metadata |> List.map header_of_slot_metadata + in + (* Slot 0 was subscribed to, but not confirmed. + Slot 1 was subscribed to and confirmed. + Slot 2 was confirmed, but not subscribed to. + Only the slot header for slot 1 will appear in the list + of confirmed slots for the rollup. *) + let expected_confirmed_slot_headers = [List.nth expected_slot_headers 1] in + Check.(confirmed_slot_headers = expected_confirmed_slot_headers) + (Check.list Check.string) + ~error_msg:"Unexpected list of slot headers (%L = %R)" ; return () let register ~protocols = diff --git a/tezt/tests/expected/dal.ml/Alpha- Testing data availability layer functionality (rollup_node_dal_headers_s.out b/tezt/tests/expected/dal.ml/Alpha- Testing data availability layer functionality (rollup_node_dal_headers_s.out index 03c8f113c10e..998a32806c97 100644 --- a/tezt/tests/expected/dal.ml/Alpha- Testing data availability layer functionality (rollup_node_dal_headers_s.out +++ b/tezt/tests/expected/dal.ml/Alpha- Testing data availability layer functionality (rollup_node_dal_headers_s.out @@ -38,9 +38,17 @@ This sequence of operations was run: "commitment_hash": "[SC_ROLLUP_COMMITMENT_HASH]" } ./tezos-sc-rollup-client-alpha rpc get /global/dal/slots -[ { "level": 2, "index": 0, +[ { "level": 4, "index": 0, "header": "sh1xjHVBc36jYtzCoktBUq1RCin8bFEwXTZxpFoS1RA5MqqBsX5Z5A2CqU7QZZQUuqdKfSsvab" }, - { "level": 2, "index": 1, + { "level": 4, "index": 1, + "header": + "sh27onybk3L4eVH8khVVk8oKZvMkjGi1YszeTLneRe5ps9LL4oqXxR12DKzTMS8tJa27w5imfP" }, + { "level": 4, "index": 2, + "header": + "sh23zGWS1EKMFiCGDYWCbGf15j3mqFuQnpoHQZwytuS3c5kKwU3ktsDS4Cz12r6443BCr4Fhpa" } ] + +./tezos-sc-rollup-client-alpha rpc get /global/dal/confirmed_slots +[ { "level": 4, "index": 1, "header": "sh27onybk3L4eVH8khVVk8oKZvMkjGi1YszeTLneRe5ps9LL4oqXxR12DKzTMS8tJa27w5imfP" } ] -- GitLab