From e091450e8d6bf011b5659126d92d2f636b49117a Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Tue, 8 Oct 2024 07:41:02 +0200 Subject: [PATCH 1/7] ADAL/Proto: refutation games for ADAL --- src/proto_alpha/lib_protocol/dal_slot_repr.ml | 16 +++++-- .../lib_protocol/dal_slot_repr.mli | 8 +++- .../lib_protocol/sc_rollup_proof_repr.ml | 42 +++++++++++++++---- .../lib_protocol/test/helpers/dal_helpers.ml | 19 +++++++-- .../lib_protocol/test/helpers/dal_helpers.mli | 1 + 5 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/proto_alpha/lib_protocol/dal_slot_repr.ml b/src/proto_alpha/lib_protocol/dal_slot_repr.ml index 04cd33a43c8e..1560b5cab2bf 100644 --- a/src/proto_alpha/lib_protocol/dal_slot_repr.ml +++ b/src/proto_alpha/lib_protocol/dal_slot_repr.ml @@ -977,7 +977,7 @@ module History = struct ensuring that we a have a cell per slot index in the skip list at every level after DAL activation. *) let produce_proof_repr dal_params page_id ~page_info ~get_history slots_hist - = + ~attestation_threshold_per_mil = let open Lwt_result_syntax in let Page.{slot_id = target_slot_id; page_index = _} = page_id in (* We first search for the slots attested at level [published_level]. *) @@ -1048,10 +1048,17 @@ module History = struct "The page ID's slot is not confirmed, but page content and \ proof are provided.") - let produce_proof dal_params page_id ~page_info ~get_history slots_hist = + let produce_proof dal_params page_id ~page_info ~get_history slots_hist + ~attestation_threshold_per_mil = let open Lwt_result_syntax in let* proof_repr, page_data = - produce_proof_repr dal_params page_id ~page_info ~get_history slots_hist + produce_proof_repr + dal_params + page_id + ~page_info + ~get_history + slots_hist + ~attestation_threshold_per_mil in let*? serialized_proof = serialize_proof proof_repr in return (serialized_proof, page_data) @@ -1143,7 +1150,8 @@ module History = struct "verify_proof_repr: the confirmation proof doesn't contain the \ attested slot." - let verify_proof dal_params page_id snapshot serialized_proof = + let verify_proof dal_params page_id snapshot serialized_proof + ~attestation_threshold_per_mil:_ = let open Result_syntax in let* proof_repr = deserialize_proof serialized_proof in verify_proof_repr dal_params page_id snapshot proof_repr diff --git a/src/proto_alpha/lib_protocol/dal_slot_repr.mli b/src/proto_alpha/lib_protocol/dal_slot_repr.mli index fa221aa3de95..6361ae381480 100644 --- a/src/proto_alpha/lib_protocol/dal_slot_repr.mli +++ b/src/proto_alpha/lib_protocol/dal_slot_repr.mli @@ -352,6 +352,7 @@ module History : sig page_info:(Page.content * Page.proof) option -> get_history:(hash -> t option Lwt.t) -> t -> + attestation_threshold_per_mil:int option -> (proof * Page.content option) tzresult Lwt.t (** [verify_proof dal_params page_id snapshot proof] verifies that the given @@ -365,7 +366,12 @@ module History : sig the candidate slot (if any). *) val verify_proof : - parameters -> Page.t -> t -> proof -> Page.content option tzresult + parameters -> + Page.t -> + t -> + proof -> + attestation_threshold_per_mil:int option -> + Page.content option tzresult type error += Add_element_in_slots_skip_list_violates_ordering diff --git a/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml index 581f1800804c..940267898502 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml @@ -291,7 +291,8 @@ module Dal_helpers = struct let verify ~metadata ~dal_activation_level ~dal_attestation_lag ~dal_number_of_slots ~commit_inbox_level dal_parameters page_id - dal_snapshot proof ~dal_attested_slots_validity_lag = + dal_snapshot proof ~dal_attested_slots_validity_lag + ~attestation_threshold_per_mil = let open Result_syntax in if page_id_is_valid @@ -310,13 +311,15 @@ module Dal_helpers = struct page_id dal_snapshot proof + ~attestation_threshold_per_mil in return_some (Sc_rollup_PVM_sig.Reveal (Dal_page input)) else return_none let produce ~metadata ~dal_activation_level ~dal_attestation_lag ~dal_number_of_slots ~commit_inbox_level dal_parameters page_id ~page_info - ~get_history confirmed_slots_history ~dal_attested_slots_validity_lag = + ~get_history confirmed_slots_history ~dal_attested_slots_validity_lag + ~attestation_threshold_per_mil = let open Lwt_result_syntax in if page_id_is_valid @@ -335,6 +338,7 @@ module Dal_helpers = struct page_id ~page_info ~get_history + ~attestation_threshold_per_mil confirmed_slots_history in return @@ -385,6 +389,7 @@ let valid (type state proof output) page_id dal_snapshot proof + ~attestation_threshold_per_mil:None |> Lwt.return | Some (Reveal_proof Dal_parameters_proof) -> (* FIXME: https://gitlab.com/tezos/tezos/-/issues/6562 @@ -437,12 +442,18 @@ let valid (type state proof output) check (Dal_slot_repr.Page.equal page_id pid) "Dal proof's page ID is not the one expected in input request." + | ( Some (Reveal_proof (Dal_page_proof {page_id; proof = _})), + Needs_reveal + (Request_adal_page {page_id = pid; attestation_threshold_per_mil = _}) + ) -> + (* ADAL/FIXME: implement refutation games for adaptive DAL. *) + (* ADAL/TODO: check that this is sufficient for Adaptive DAL. *) + check + (Dal_slot_repr.Page.equal page_id pid) + "Dal proof's page ID is not the one expected in input request." | ( Some (Reveal_proof Dal_parameters_proof), Needs_reveal Reveal_dal_parameters ) -> return_unit - | Some (Reveal_proof _), Needs_reveal (Request_dal_page _pid) -> - (* ADAL/FIXME: implement refutation games for adaptive DAL. *) - assert false | None, (Initial | First_after _ | Needs_reveal _) | Some _, No_input_required | Some (Inbox_proof _), Needs_reveal _ @@ -568,10 +579,25 @@ let produce ~metadata pvm_and_state commit_inbox_level ~is_reveal_enabled = ~page_info ~get_history ~dal_attested_slots_validity_lag + ~attestation_threshold_per_mil:None + confirmed_slots_history + | Needs_reveal (Request_adal_page {page_id; attestation_threshold_per_mil}) + -> + (* ADAL/FIXME: check it's correct *) + let open Dal_with_history in + Dal_helpers.produce + ~dal_number_of_slots + ~metadata + ~dal_activation_level + dal_parameters + ~dal_attestation_lag + ~commit_inbox_level + page_id + ~page_info + ~get_history + ~dal_attested_slots_validity_lag + ~attestation_threshold_per_mil:(Some attestation_threshold_per_mil) confirmed_slots_history - | Needs_reveal (Request_adal_page _info) -> - (* ADAL/FIXME: Implement refutations for adaptive DAL *) - assert false | Needs_reveal Reveal_dal_parameters -> let open Dal_with_history in return diff --git a/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.ml index 87f4a660fad6..a434b1c5904d 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.ml @@ -183,17 +183,30 @@ struct if any. The result of each step is checked with [check_produce_result] and [check_verify_result], respectively. *) let produce_and_verify_proof ~check_produce ?check_verify ~get_history - skip_list ~page_info ~page_id = + skip_list ~page_info ~page_id ~attestation_threshold_per_mil = let open Lwt_result_wrap_syntax in let*!@ res = - Hist.produce_proof params ~page_info page_id ~get_history skip_list + Hist.produce_proof + params + ~page_info + page_id + ~get_history + skip_list + ~attestation_threshold_per_mil in let* () = check_produce res page_info in match check_verify with | None -> return_unit | Some check_verify -> let*? proof, _input_opt = res in - let@ res = Hist.verify_proof params page_id skip_list proof in + let@ res = + Hist.verify_proof + params + page_id + skip_list + proof + ~attestation_threshold_per_mil + in check_verify res page_info (* Some check functions. *) diff --git a/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.mli b/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.mli index 29b721cdc1a0..57805eb53a26 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.mli @@ -143,6 +143,7 @@ end) : sig Dal_slot_repr.History.t -> page_info:(bytes * Cryptobox.page_proof) option -> page_id:Dal_slot_repr.Page.t -> + attestation_threshold_per_mil:int option -> (unit, tztrace) result Lwt.t (** Check if two page proofs are equal. *) -- GitLab From 80fbb5e0ea66af59516baf73ad8be9dd880cf803 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Tue, 8 Oct 2024 08:01:11 +0200 Subject: [PATCH 2/7] ADAL/Proto: revamp skip list content again --- src/proto_alpha/lib_protocol/dal_slot_repr.ml | 124 +++++++----------- 1 file changed, 50 insertions(+), 74 deletions(-) diff --git a/src/proto_alpha/lib_protocol/dal_slot_repr.ml b/src/proto_alpha/lib_protocol/dal_slot_repr.ml index 1560b5cab2bf..3a5aee6d7df0 100644 --- a/src/proto_alpha/lib_protocol/dal_slot_repr.ml +++ b/src/proto_alpha/lib_protocol/dal_slot_repr.ml @@ -314,17 +314,18 @@ module History = struct (** Each cell of the skip list is either a slot header that has been attested, or a published level and a slot index for which no slot header is attested (so, no associated commitment). *) - type attestation_status = Unattested of Header.id | Attested of Header.t - type t = { - attestation_status : attestation_status; - attested_shards : int; - total_shards : int; - } + type t = + | Unpublished of Header.id + | Published of { + slot_header : Header.t; + proto_attested : bool; + attestation_ratio : Q.t; + } let content_id = function - | {attestation_status = Unattested slot_id; _} -> slot_id - | {attestation_status = Attested {id; _}; _} -> id + | Unpublished slot_id -> slot_id + | Published {slot_header = {id; _}; _} -> id let encoding = let open Data_encoding in @@ -332,95 +333,70 @@ module History = struct ~tag_size:`Uint8 [ case - ~title:"unattested" + ~title:"unpublished" (Tag 0) (merge_objs - (obj3 - (req "kind" (constant "unattested")) - (req "attested_shards" int31) - (req "total_shards" int31)) + (obj1 (req "kind" (constant "unpublished"))) Header.id_encoding) (function - | { - attestation_status = Unattested slot_id; - attested_shards; - total_shards; - } -> - Some (((), attested_shards, total_shards), slot_id) - | {attestation_status = Attested _; _} -> None) - (fun (((), attested_shards, total_shards), slot_id) -> - { - attestation_status = Unattested slot_id; - attested_shards; - total_shards; - }); + | Unpublished slot_id -> Some ((), slot_id) | Published _ -> None) + (fun ((), slot_id) -> Unpublished slot_id); case - ~title:"attested" + ~title:"published" (Tag 1) (merge_objs - (obj3 - (req "kind" (constant "attested")) - (req "attested_shards" int31) - (req "total_shards" int31)) + (obj4 + (req "kind" (constant "published")) + (req "proto_attested" bool) + (req "attestation_ratio_num" z) + (req "attestation_ratio_den" z)) Header.encoding) (function - | {attestation_status = Unattested _; _} -> None - | { - attestation_status = Attested slot_header; - attested_shards; - total_shards; - } -> - Some (((), attested_shards, total_shards), slot_header)) - (fun (((), attested_shards, total_shards), slot_header) -> - { - attestation_status = Attested slot_header; - attested_shards; - total_shards; - }); + | Unpublished _ -> None + | Published + { + slot_header; + proto_attested; + attestation_ratio = Q.{num; den}; + } -> + Some (((), proto_attested, num, den), slot_header)) + (fun (((), proto_attested, num, den), slot_header) -> + Published + { + slot_header; + proto_attested; + attestation_ratio = Q.make num den; + }); ] - let equal_attestation_status t1 t2 = - match (t1, t2) with - | Unattested sid1, Unattested sid2 -> Header.slot_id_equal sid1 sid2 - | Attested sh1, Attested sh2 -> Header.equal sh1 sh2 - | Unattested _, _ | Attested _, _ -> false - let equal t1 t2 = - equal_attestation_status t1.attestation_status t2.attestation_status + match (t1, t2) with + | Unpublished sid1, Unpublished sid2 -> Header.slot_id_equal sid1 sid2 + | ( Published {slot_header; proto_attested; attestation_ratio}, + Published sh2 ) -> + Header.equal slot_header sh2.slot_header + && Compare.Bool.equal proto_attested sh2.proto_attested + && Q.equal attestation_ratio sh2.attestation_ratio + | Unpublished _, _ | Published _, _ -> false let zero, zero_level = let zero_level = Raw_level_repr.root in let zero_index = Dal_slot_index_repr.zero in - ( { - attestation_status = - Unattested {published_level = zero_level; index = zero_index}; - attested_shards = 0; - total_shards = 1; - }, + ( Unpublished {published_level = zero_level; index = zero_index}, zero_level ) let pp fmt = function - | {attestation_status = Unattested slot_id; attested_shards; total_shards} - -> - Format.fprintf - fmt - "Unattested (%a, %d/%d)" - Header.pp_id - slot_id - attested_shards - total_shards - | { - attestation_status = Attested slot_header; - attested_shards; - total_shards; - } -> + | Unpublished slot_id -> + Format.fprintf fmt "Unpublished (%a)" Header.pp_id slot_id + | Published {slot_header; proto_attested; attestation_ratio} -> Format.fprintf fmt - "Attested (%a, %d/%d)" + "Published (%a, attested:%b, attestation_ratio:%a)" Header.pp slot_header - attested_shards - total_shards + proto_attested + Q.pp_print + attestation_ratio end module Skip_list = struct -- GitLab From 3fbde5c387f963b73e7da6f8e9f0c07df351ab2d Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Tue, 8 Oct 2024 08:09:11 +0200 Subject: [PATCH 3/7] ADAL/Proto: revamp building/adding of skip list cells --- src/proto_alpha/lib_protocol/dal_slot_repr.ml | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/proto_alpha/lib_protocol/dal_slot_repr.ml b/src/proto_alpha/lib_protocol/dal_slot_repr.ml index 3a5aee6d7df0..aaea31f6a083 100644 --- a/src/proto_alpha/lib_protocol/dal_slot_repr.ml +++ b/src/proto_alpha/lib_protocol/dal_slot_repr.ml @@ -602,45 +602,36 @@ module History = struct let* all_indices = I.slots_range ~number_of_slots ~lower:0 ~upper:(number_of_slots - 1) in - let mk_unattested attested_shards total_shards index = - Content. - { - attestation_status = Unattested Header.{published_level; index}; - attested_shards; - total_shards; - } + let mk_unpublished index = + Content.Unpublished Header.{published_level; index} in (* Hypothesis: both lists are sorted in increasing order w.r.t. slots indices. *) let rec aux indices slots = match (indices, slots) with - | _, [] -> List.map (mk_unattested 0 1) indices |> ok + | _, [] -> List.map mk_unpublished indices |> ok | [], _s :: _ -> tzfail Add_element_in_slots_skip_list_violates_ordering | i :: indices', elt :: slots' -> let s, (s_is_attested, s_attested_shards, s_total_shards) = elt in + (* ADAL/TODO: provide the ratio directly in the arguments. *) + let attestation_ratio = + Q.make (Z.of_int s_attested_shards) (Z.of_int s_total_shards) + in if I.(i = s.Header.id.index) then let* res = aux indices' slots' in - if s_is_attested then - Content. - { - attestation_status = Content.Attested s; - attested_shards = s_attested_shards; - total_shards = s_total_shards; - } - :: res - |> ok - else - Content. + + Content.( + Published { - attestation_status = Content.Unattested s.id; - attested_shards = s_attested_shards; - total_shards = s_total_shards; - } - :: res - |> ok + slot_header = s; + proto_attested = s_is_attested; + attestation_ratio; + }) + :: res + |> ok else if I.(i < s.Header.id.index) then let* res = aux indices' slots in - mk_unattested 0 1 i :: res |> ok + mk_unpublished i :: res |> ok else (* i > s.Header.id.index *) tzfail Add_element_in_slots_skip_list_violates_ordering -- GitLab From 3fb21b8458e074066020866f59f57e6c4cf2c039 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Tue, 8 Oct 2024 09:03:52 +0200 Subject: [PATCH 4/7] ADAL/Proto: revamp producing DAL proofs --- src/proto_alpha/lib_protocol/dal_slot_repr.ml | 98 ++++++++++++++----- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/src/proto_alpha/lib_protocol/dal_slot_repr.ml b/src/proto_alpha/lib_protocol/dal_slot_repr.ml index aaea31f6a083..c4a1322ea0c0 100644 --- a/src/proto_alpha/lib_protocol/dal_slot_repr.ml +++ b/src/proto_alpha/lib_protocol/dal_slot_repr.ml @@ -936,6 +936,16 @@ module History = struct page_size = Bytes.length data; } + let check_is_attested ~proto_attested ~attestation_ratio + ~attestation_threshold_ratio = + match attestation_threshold_ratio with + | None -> + (* We target regular DAL version *) + proto_attested + | Some attestation_threshold_ratio -> + (* We rely on adaptive DAL for attestation status *) + Q.geq attestation_ratio attestation_threshold_ratio + (** The [produce_proof_repr] function assumes that some invariants hold, such as: - The DAL has been activated, - The level of [page_id] is after the DAL activation level. @@ -946,6 +956,12 @@ module History = struct let produce_proof_repr dal_params page_id ~page_info ~get_history slots_hist ~attestation_threshold_per_mil = let open Lwt_result_syntax in + let attestation_threshold_ratio = + Option.map + (fun e -> Q.make (Z.of_int e) (Z.of_int 1000)) + attestation_threshold_per_mil + in + let Page.{slot_id = target_slot_id; page_index = _} = page_id in (* We first search for the slots attested at level [published_level]. *) let*! search_result = @@ -976,40 +992,76 @@ module History = struct produce proofs are supposed to be in the skip list." | Found target_cell -> ( let inc_proof = List.rev search_result.Skip_list.rev_path in + (* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + ADAL/TODO: Refactor this match to reduce cases by adding a function + is_attested that checks directly if Published & attested or + attested_threshold reached. If yes, returned the commitment (and + needed info), and match with page_info. + + XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX *) match (page_info, Skip_list.content target_cell) with | ( Some (page_data, page_proof), - {attestation_status = Attested {commitment; id = _}; _} ) -> + Published + { + slot_header = {commitment; id = _}; + proto_attested; + attestation_ratio; + } ) -> (* The case where the slot to which the page is supposed to belong is found and the page's information are given. *) - let*? () = - (* We check the page's proof against the commitment. *) - check_page_proof - dal_params - page_proof - page_data - page_id - commitment - in - (* All checks succeeded. We return a `Page_confirmed` proof. *) - return - ( Page_confirmed {target_cell; inc_proof; page_data; page_proof}, - Some page_data ) - | None, {attestation_status = Unattested _; _} -> + if + check_is_attested + ~proto_attested + ~attestation_ratio + ~attestation_threshold_ratio + then + let*? () = + (* We check the page's proof against the commitment. *) + check_page_proof + dal_params + page_proof + page_data + page_id + commitment + in + (* All checks succeeded. We return a `Page_confirmed` proof. *) + return + ( Page_confirmed + {target_cell; inc_proof; page_data; page_proof}, + Some page_data ) + else + tzfail + @@ dal_proof_error + "The page ID's slot is not confirmed, but page content \ + and proof are provided." + | None, Unpublished _ -> (* The slot corresponding to the given page's index is not found in the attested slots of the page's level, and no information is given for that page. So, we produce a proof that the page is not attested. *) return (Page_unconfirmed {target_cell; inc_proof}, None) - | None, {attestation_status = Attested _; _} -> - (* Mismatch: case where no page information are given, but the - slot is attested. *) - tzfail - @@ dal_proof_error - "The page ID's slot is confirmed, but no page content and \ - proof are provided." - | Some _, {attestation_status = Unattested _; _} -> + | None, Published {slot_header = _; proto_attested; attestation_ratio} + -> + if + check_is_attested + ~proto_attested + ~attestation_ratio + ~attestation_threshold_ratio + then + (* Mismatch: case where no page information are given, but the + slot is attested. *) + tzfail + @@ dal_proof_error + "The page ID's slot is confirmed, but no page content and \ + proof are provided." + else return (Page_unconfirmed {target_cell; inc_proof}, None) + | Some _, Unpublished _ -> (* Mismatch: case where page information are given, but the slot is not attested. *) + (* Carefully check this case. We should not allow unpublished slots' + pages to be imported with Adaptive DAL and attestation ratio = + 0. *) tzfail @@ dal_proof_error "The page ID's slot is not confirmed, but page content and \ -- GitLab From 68e48cfe0b21d2a365d09a29def3899aa0d036a1 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Tue, 8 Oct 2024 11:39:54 +0200 Subject: [PATCH 5/7] ADAL/Proto: adapt skip list content for verify_proof case --- src/proto_alpha/lib_protocol/dal_slot_repr.ml | 96 ++++++++++++++----- .../lib_protocol/dal_slot_repr.mli | 17 ++-- .../lib_protocol/test/helpers/dal_helpers.ml | 6 +- 3 files changed, 85 insertions(+), 34 deletions(-) diff --git a/src/proto_alpha/lib_protocol/dal_slot_repr.ml b/src/proto_alpha/lib_protocol/dal_slot_repr.ml index c4a1322ea0c0..1062bdade2a2 100644 --- a/src/proto_alpha/lib_protocol/dal_slot_repr.ml +++ b/src/proto_alpha/lib_protocol/dal_slot_repr.ml @@ -1104,8 +1104,14 @@ module History = struct path) (dal_proof_error "verify_proof_repr: invalid inclusion Dal proof.") - let verify_proof_repr dal_params page_id snapshot proof = + let verify_proof_repr dal_params page_id snapshot proof + ~attestation_threshold_per_mil = let open Result_syntax in + let attestation_threshold_ratio = + Option.map + (fun e -> Q.make (Z.of_int e) (Z.of_int 1000)) + attestation_threshold_per_mil + in let Page.{slot_id = Header.{published_level; index}; page_index = _} = page_id in @@ -1153,38 +1159,82 @@ module History = struct verify_inclusion_proof inc_proof ~src:snapshot ~dest:target_cell in match (page_proof_check, cell_content) with - | None, {attestation_status = Unattested _; _} -> return_none + | None, Content.Unpublished _ -> return_none | ( Some page_proof_check, - {attestation_status = Attested {commitment; _}; _} ) -> - let* page_data = page_proof_check commitment in - return_some page_data - | Some _, {attestation_status = Unattested _; _} -> + Content.Published + {slot_header = {commitment; _}; proto_attested; attestation_ratio} ) + -> + (* Old versions: + + | ( Some page_proof_check, + {attestation_status = Attested {commitment; _}; _} ) -> + let* page_data = page_proof_check commitment in + return_some page_data + | Some _, {attestation_status = Unattested _; _} -> + error + @@ dal_proof_error + "verify_proof_repr: the unconfirmation proof contains the \ + target slot." + *) + if + check_is_attested + ~proto_attested + ~attestation_ratio + ~attestation_threshold_ratio + then + let* page_data = page_proof_check commitment in + return_some page_data + else + error + @@ dal_proof_error + "verify_proof_repr: the unconfirmation proof contains the \ + target slot (1)." + | Some _, Content.Unpublished _ -> error @@ dal_proof_error "verify_proof_repr: the unconfirmation proof contains the \ - target slot." - | None, {attestation_status = Attested _; _} -> - error - @@ dal_proof_error - "verify_proof_repr: the confirmation proof doesn't contain the \ - attested slot." + target slot (2)." + | ( None, + Content.Published {slot_header = _; proto_attested; attestation_ratio} + ) -> + (* Old version: + | None, {attestation_status = Attested _; _} -> + error + @@ dal_proof_error + "verify_proof_repr: the confirmation proof doesn't contain the \ + attested slot." + *) + if + check_is_attested + ~proto_attested + ~attestation_ratio + ~attestation_threshold_ratio + then + error + @@ dal_proof_error + "verify_proof_repr: the confirmation proof doesn't contain \ + the attested slot." + else return_none let verify_proof dal_params page_id snapshot serialized_proof - ~attestation_threshold_per_mil:_ = + ~attestation_threshold_per_mil = let open Result_syntax in let* proof_repr = deserialize_proof serialized_proof in - verify_proof_repr dal_params page_id snapshot proof_repr + verify_proof_repr + dal_params + page_id + snapshot + proof_repr + ~attestation_threshold_per_mil module Internal_for_tests = struct - type cell_attestation_status = Content.attestation_status = - | Unattested of Header.id - | Attested of Header.t - - type cell_content = Content.t = { - attestation_status : cell_attestation_status; - attested_shards : int; - total_shards : int; - } + type cell_content = Content.t = + | Unpublished of Header.id + | Published of { + slot_header : Header.t; + proto_attested : bool; + attestation_ratio : Q.t; + } let content = Skip_list.content diff --git a/src/proto_alpha/lib_protocol/dal_slot_repr.mli b/src/proto_alpha/lib_protocol/dal_slot_repr.mli index 6361ae381480..970dc4ea9018 100644 --- a/src/proto_alpha/lib_protocol/dal_slot_repr.mli +++ b/src/proto_alpha/lib_protocol/dal_slot_repr.mli @@ -384,15 +384,14 @@ module History : sig headers directly to refactor the common [published_level] and save space. This is important for refutation proofs, as they have to fit in an L1 operation. *) - type cell_attestation_status = - | Unattested of Header.id - | Attested of Header.t - - type cell_content = { - attestation_status : cell_attestation_status; - attested_shards : int; - total_shards : int; - } + + type cell_content = + | Unpublished of Header.id + | Published of { + slot_header : Header.t; + proto_attested : bool; + attestation_ratio : Q.t; + } (** Returns the content of the last cell in the given skip list. *) val content : t -> cell_content diff --git a/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.ml index a434b1c5904d..eb458921a76c 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/dal_helpers.ml @@ -69,8 +69,10 @@ let derive_dal_parameters (reference : Cryptobox.parameters) ~redundancy_factor } let content_slot_id v = - match v.Hist.Internal_for_tests.attestation_status with - | Hist.Internal_for_tests.Unattested id | Attested {id; _} -> id + match v with + | Hist.Internal_for_tests.Unpublished id + | Hist.Internal_for_tests.Published {slot_header = {id; _}; _} -> + id module Make (Parameters : sig val dal_parameters : Alpha_context.Constants.Parametric.dal -- GitLab From cbac0cd786857c75cfe73bce34f3adfcd5a429b8 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Tue, 8 Oct 2024 13:22:01 +0200 Subject: [PATCH 6/7] ADAL/Proto: adapt refutation game to handle refutations with ADAL pages --- .../lib_sc_rollup_node/dal_pages_request.ml | 109 ++++++++++++++--- .../lib_sc_rollup_node/dal_pages_request.mli | 1 + .../refutation_game_helpers.ml | 112 +++++++++++------- 3 files changed, 164 insertions(+), 58 deletions(-) diff --git a/src/proto_alpha/lib_sc_rollup_node/dal_pages_request.ml b/src/proto_alpha/lib_sc_rollup_node/dal_pages_request.ml index 9e488514ac74..ef32072d8e29 100644 --- a/src/proto_alpha/lib_sc_rollup_node/dal_pages_request.ml +++ b/src/proto_alpha/lib_sc_rollup_node/dal_pages_request.ml @@ -94,7 +94,7 @@ module Event = struct ~pp5:pp_content_elipsis end -let store_entry_from_published_level ~dal_attestation_lag ~published_level +let _store_entry_from_published_level ~dal_attestation_lag ~published_level node_ctxt = Node_context.hash_of_level node_ctxt @@ Int32.( @@ -188,7 +188,7 @@ let page_id_is_valid let slot_pages (dal_constants : Octez_smart_rollup.Rollup_constants.dal_constants) ~dal_activation_level ~inbox_level node_ctxt slot_id - ~dal_attested_slots_validity_lag = + ~dal_attested_slots_validity_lag ~adal_attestation_threshold_per_mil = let open Lwt_result_syntax in let Node_context.{genesis_info = {level = origination_level; _}; _} = node_ctxt @@ -205,24 +205,101 @@ let slot_pages slot_id then return_none else - let* confirmed_in_block_hash = - store_entry_from_published_level - ~dal_attestation_lag:dal_constants.attestation_lag - ~published_level - node_ctxt + let cctxt = node_ctxt.cctxt in + let attestation_level = + Int32.add + (Raw_level.to_int32 published_level) + (Int32.of_int dal_constants.attestation_lag) + in + let* attestations_statuses = + Plugin.RPC.Dal.dal_published_slot_headers_status + (new Protocol_client_context.wrap_full cctxt) + (cctxt#chain, `Level attestation_level) + () in let index = Sc_rollup_proto_types.Dal.Slot_index.to_octez index in - let* processed = - Node_context.find_slot_status node_ctxt ~confirmed_in_block_hash index + + let target_slot_attestation_status = + List.filter + (fun ((slot_header : Sc_rollup_proto_types.Dal.Slot_header.t), _) -> + let id = slot_header.id in + Raw_level.equal id.published_level slot_id.published_level + && Dal.Slot_index.equal id.index slot_id.index) + attestations_statuses in - match processed with - | Some `Confirmed -> - let* pages = - download_confirmed_slot_pages node_ctxt ~published_level ~index + match + (target_slot_attestation_status, adal_attestation_threshold_per_mil) + with + | [], _ -> + Format.eprintf "#### [Refutation] Slot not published at all@." ; + return_none + | _ :: _ :: _, _ -> storage_invariant_broken published_level index + | [(_slot_header, (attested, _num_attested_shards, _total_shards))], None -> + if attested then + let () = + Format.eprintf "#### [Refutation] DAL Slot published and attested@." + in + let+ res = + download_confirmed_slot_pages node_ctxt ~published_level ~index + in + Some res + else + let () = + Format.eprintf + "#### [Refutation] DAL Slot published not NOT attested@." + in + return_none + | ( [(_slot_header, (_attested, num_attested_shards, total_shards))], + Some attestation_threshold_per_mil ) -> + let threshold = + Q.make (Z.of_int attestation_threshold_per_mil) (Z.of_int 1000) in - return (Some pages) - | Some `Unconfirmed -> return_none - | None -> storage_invariant_broken published_level index + let current = + Q.make (Z.of_int num_attested_shards) (Z.of_int total_shards) + in + let is_accepted = Q.compare current threshold >= 0 in + if is_accepted then + let () = + Format.eprintf + "#### [Refutation] Adaptive DAL Slot published and sufficiently \ + attested. Threshold:%a, Current:%a@." + Q.pp_print + threshold + Q.pp_print + current + in + let+ res = + download_confirmed_slot_pages node_ctxt ~published_level ~index + in + Some res + else + let () = + Format.eprintf + "#### [Refutation] DAL Slot published but NOT sufficiently \ + attested@." + in + return_none + +(* + let* confirmed_in_block_hash = + store_entry_from_published_level + ~dal_attestation_lag:dal_constants.attestation_lag + ~published_level + node_ctxt + in + let index = Sc_rollup_proto_types.Dal.Slot_index.to_octez index in + let* processed = + Node_context.find_slot_status node_ctxt ~confirmed_in_block_hash index + in + match processed with + | Some `Confirmed -> + let* pages = + download_confirmed_slot_pages node_ctxt ~published_level ~index + in + return (Some pages) + | Some `Unconfirmed -> return_none + | None -> storage_invariant_broken published_level index +*) let get_page node_ctxt ~inbox_level page_id = let open Lwt_result_syntax in diff --git a/src/proto_alpha/lib_sc_rollup_node/dal_pages_request.mli b/src/proto_alpha/lib_sc_rollup_node/dal_pages_request.mli index f5d2229b45da..71d67aecfa57 100644 --- a/src/proto_alpha/lib_sc_rollup_node/dal_pages_request.mli +++ b/src/proto_alpha/lib_sc_rollup_node/dal_pages_request.mli @@ -64,6 +64,7 @@ val slot_pages : _ Node_context.t -> Dal.slot_id -> dal_attested_slots_validity_lag:int -> + adal_attestation_threshold_per_mil:int option -> Dal.Page.content list option tzresult Lwt.t (** Retrieve the content of the page identified by the given ID from the store. diff --git a/src/proto_alpha/lib_sc_rollup_node/refutation_game_helpers.ml b/src/proto_alpha/lib_sc_rollup_node/refutation_game_helpers.ml index 7ea4b3650dc3..58d2341083bc 100644 --- a/src/proto_alpha/lib_sc_rollup_node/refutation_game_helpers.ml +++ b/src/proto_alpha/lib_sc_rollup_node/refutation_game_helpers.ml @@ -28,6 +28,54 @@ open Protocol open Alpha_context +let do_it constants (node_ctxt : _ Node_context.t) ~inbox_level + (dal_params : Dal.parameters) ~dal_activation_level + ~dal_attested_slots_validity_lag ~adal_attestation_threshold_per_mil page_id + = + let open Lwt_result_syntax in + let Dal.Page.{slot_id; page_index} = page_id in + let* pages = + Dal_pages_request.slot_pages + constants.Rollup_constants.dal + ~dal_activation_level + ~dal_attested_slots_validity_lag + ~inbox_level + node_ctxt + ~adal_attestation_threshold_per_mil + slot_id + in + match pages with + | None -> return_none (* The slot is not confirmed. *) + | Some pages -> ( + let pages_per_slot = dal_params.slot_size / dal_params.page_size in + (* check invariant that pages' length is correct. *) + (* FIXME/DAL: https://gitlab.com/tezos/tezos/-/issues/4031 + It's better to do the check when the slots are saved into disk. *) + (* FIXME/DAL: https://gitlab.com/tezos/tezos/-/issues/3997 + This check is not resilient to dal parameters change. *) + match List.nth_opt pages page_index with + | Some content -> + let* page_proof = + let dal_cctxt = + WithExceptions.Option.get ~loc:__LOC__ node_ctxt.dal_cctxt + in + let Dal.Slot.Header.{published_level; index} = slot_id in + Dal_node_client.get_slot_page_proof + dal_cctxt + { + slot_level = published_level |> Raw_level.to_int32; + slot_index = index |> Alpha_context.Dal.Slot_index.to_int; + } + page_index + in + return_some (content, page_proof) + | None -> + failwith + "Page index %d too big or negative.\n\ + Number of pages in a slot is %d." + page_index + pages_per_slot) + (** When the PVM is waiting for a Dal page input, this function attempts to retrieve the page's content from the store, the data of its slot. Then it computes the proof that the page is part of the slot and returns the @@ -71,48 +119,28 @@ let page_info_from_pvm_state constants (node_ctxt : _ Node_context.t) (Ctxt_wrapper.of_node_pvmstate start_state) in match input_request with - | Sc_rollup.(Needs_reveal (Request_dal_page page_id)) -> ( - let Dal.Page.{slot_id; page_index} = page_id in - let* pages = - Dal_pages_request.slot_pages - constants.Rollup_constants.dal - ~dal_activation_level - ~dal_attested_slots_validity_lag - ~inbox_level - node_ctxt - slot_id - in - match pages with - | None -> return_none (* The slot is not confirmed. *) - | Some pages -> ( - let pages_per_slot = dal_params.slot_size / dal_params.page_size in - (* check invariant that pages' length is correct. *) - (* FIXME/DAL: https://gitlab.com/tezos/tezos/-/issues/4031 - It's better to do the check when the slots are saved into disk. *) - (* FIXME/DAL: https://gitlab.com/tezos/tezos/-/issues/3997 - This check is not resilient to dal parameters change. *) - match List.nth_opt pages page_index with - | Some content -> - let* page_proof = - let dal_cctxt = - WithExceptions.Option.get ~loc:__LOC__ node_ctxt.dal_cctxt - in - let Dal.Slot.Header.{published_level; index} = slot_id in - Dal_node_client.get_slot_page_proof - dal_cctxt - { - slot_level = published_level |> Raw_level.to_int32; - slot_index = index |> Alpha_context.Dal.Slot_index.to_int; - } - page_index - in - return_some (content, page_proof) - | None -> - failwith - "Page index %d too big or negative.\n\ - Number of pages in a slot is %d." - page_index - pages_per_slot)) + | Sc_rollup.(Needs_reveal (Request_dal_page page_id)) -> + do_it + constants + (node_ctxt : _ Node_context.t) + ~inbox_level + (dal_params : Dal.parameters) + ~dal_activation_level + ~dal_attested_slots_validity_lag + ~adal_attestation_threshold_per_mil:None + page_id + | Sc_rollup.( + Needs_reveal (Request_adal_page {page_id; attestation_threshold_per_mil})) + -> + do_it + constants + node_ctxt + ~inbox_level + dal_params + ~dal_activation_level + ~dal_attested_slots_validity_lag + ~adal_attestation_threshold_per_mil:(Some attestation_threshold_per_mil) + page_id | _ -> return_none let metadata (node_ctxt : _ Node_context.t) = -- GitLab From 3305e7d85c658a9a19ba15d912f9fdbd263efee7 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Tue, 8 Oct 2024 07:40:51 +0200 Subject: [PATCH 7/7] ADAL/Tezt: refutation tests with Adaptive DAL echo kernel --- tezt/tests/dal.ml | 54 +++++++++++++++---- ...ith L1 (test adaptive DAL echo_kernel).out | 39 ++++++++++++++ 2 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 tezt/tests/expected/dal.ml/Alpha- Testing DAL rollup and node with L1 (test adaptive DAL echo_kernel).out diff --git a/tezt/tests/dal.ml b/tezt/tests/dal.ml index b34b4a51818f..fa0cccc50a7e 100644 --- a/tezt/tests/dal.ml +++ b/tezt/tests/dal.ml @@ -7361,7 +7361,7 @@ module Refutations = struct - Initializes two DAL nodes, one honest and one faulty. - Initializes two rollup nodes, one for each DAL node. - Prepares and provides the kernel code for both rollup nodes. - - Originates the dal_echo_kernel smart rollup. + - Originates the xdal_echo_kernel smart rollup. - Publishes DAL slots at indices 0 and 1 of the same level. - Attest the two slots. - Stops the faulty DAL node and alters its shards store to make it return @@ -7377,7 +7377,7 @@ module Refutations = struct *) let scenario_with_two_rollups_a_faulty_dal_node_and_a_correct_one ~refute_operations_priority _protocol parameters _dal_node _sc_rollup_node - _sc_rollup_address node client pvm_name = + _sc_rollup_address node client pvm_name ~xdal_echo_kernel = (* Initializing the real SRS. *) let faulty_operator_key = Constant.bootstrap4.public_key_hash in let honest_operator_key = Constant.bootstrap5.public_key_hash in @@ -7422,7 +7422,7 @@ module Refutations = struct Sc_rollup_helpers.prepare_installer_kernel ~preimages_dir: (Filename.concat (Sc_rollup_node.data_dir sc_rollup) pvm_name) - Constant.WASM.dal_echo_kernel + xdal_echo_kernel in return boot_sector) "" @@ -7436,11 +7436,11 @@ module Refutations = struct bake_for ~count:parameters.Dal.Parameters.attestation_lag client in - (* Originate the dal_echo_kernel smart rollup *) + (* Originate the xdal_echo_kernel smart rollup *) let* sc_rollup_address = Client.Sc_rollup.originate ~burn_cap:Tez.(of_int 9999999) - ~alias:"dal_echo_kernel" + ~alias:"xdal_echo_kernel" ~src:Constant.bootstrap1.public_key_hash ~kind:pvm_name ~boot_sector @@ -8461,9 +8461,10 @@ let register ~protocols = ~pvm_name:"wasm_2_0_0" ~commitment_period:5 (Refutations.scenario_with_two_rollups_a_faulty_dal_node_and_a_correct_one - ~refute_operations_priority:`Faulty_first) - ~smart_rollup_timeout_period_in_blocks:20 - ~l1_history_mode:Default_with_refutation + ~refute_operations_priority:`Faulty_first + ~xdal_echo_kernel:Constant.WASM.dal_echo_kernel) + ~smart_rollup_timeout_period_in_blocks:400 + ~l1_history_mode:(Custom Archive) ~tags:[Tag.slow] protocols ; @@ -8475,9 +8476,40 @@ let register ~protocols = ~pvm_name:"wasm_2_0_0" ~commitment_period:5 (Refutations.scenario_with_two_rollups_a_faulty_dal_node_and_a_correct_one - ~refute_operations_priority:`Honest_first) - ~smart_rollup_timeout_period_in_blocks:20 - ~l1_history_mode:Default_with_refutation + ~refute_operations_priority:`Honest_first + ~xdal_echo_kernel:Constant.WASM.dal_echo_kernel) + ~smart_rollup_timeout_period_in_blocks:400 + ~l1_history_mode:(Custom Archive) + ~tags:[Tag.slow] + protocols ; + + scenario_with_all_nodes + "Adaptive DAL refutation where the faulty node timeouts" + ~regression:false + ~uses:(fun _protocol -> + [Constant.smart_rollup_installer; Constant.WASM.adal_echo_kernel]) + ~pvm_name:"wasm_2_0_0" + ~commitment_period:5 + (Refutations.scenario_with_two_rollups_a_faulty_dal_node_and_a_correct_one + ~refute_operations_priority:`Faulty_first + ~xdal_echo_kernel:Constant.WASM.adal_echo_kernel) + ~smart_rollup_timeout_period_in_blocks:400 + ~l1_history_mode:(Custom Archive) + ~tags:[Tag.slow] + protocols ; + + scenario_with_all_nodes + "Adaptive DAL refutation where the honest node makes final move" + ~regression:false + ~uses:(fun _protocol -> + [Constant.smart_rollup_installer; Constant.WASM.adal_echo_kernel]) + ~pvm_name:"wasm_2_0_0" + ~commitment_period:5 + (Refutations.scenario_with_two_rollups_a_faulty_dal_node_and_a_correct_one + ~refute_operations_priority:`Honest_first + ~xdal_echo_kernel:Constant.WASM.adal_echo_kernel) + ~smart_rollup_timeout_period_in_blocks:400 + ~l1_history_mode:(Custom Archive) ~tags:[Tag.slow] protocols ; diff --git a/tezt/tests/expected/dal.ml/Alpha- Testing DAL rollup and node with L1 (test adaptive DAL echo_kernel).out b/tezt/tests/expected/dal.ml/Alpha- Testing DAL rollup and node with L1 (test adaptive DAL echo_kernel).out new file mode 100644 index 000000000000..2f6f670223d3 --- /dev/null +++ b/tezt/tests/expected/dal.ml/Alpha- Testing DAL rollup and node with L1 (test adaptive DAL echo_kernel).out @@ -0,0 +1,39 @@ + +./octez-client --wait none originate smart rollup rollup from '[PUBLIC_KEY_HASH]' of kind wasm_2_0_0 of type string with kernel --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1930.030 units (will add 100 for safety) +Estimated storage: 6552 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + octez-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000441 + Expected counter: 1 + Gas limit: 2031 + Storage limit: 6572 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000441 + payload fees(the block proposer) ....... +ꜩ0.000441 + Smart rollup origination: + Kind: wasm_2_0_0 + Parameter type: string + Kernel Blake2B hash: '0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8' + This smart rollup origination was successfully applied + Consumed gas: 1929.997 + Storage size: 6552 bytes + Address: [SMART_ROLLUP_HASH] + Genesis commitment hash: [SC_ROLLUP_COMMITMENT_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.638 + storage fees ........................... +ꜩ1.638 + +Smart rollup [SMART_ROLLUP_HASH] memorized as "rollup" +GET http://[HOST]:[PORT]/global/block/head/durable/wasm_2_0_0/value?key=/output/slot-0 +200 OK +"68656c6c6f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + -- GitLab