Raw File
dal_slot_repr.mli
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com>                *)
(*                                                                           *)
(* 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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

(** To verify the proof of a page membership in its associated slot, the
     Cryptobox module needs the following Dal parameters. These are part of the
     protocol's parameters. See {!Dal.Config.default}. *)
type parameters = Dal.parameters = {
  redundancy_factor : int;
  page_size : int;
  slot_size : int;
  number_of_shards : int;
}

(** An encoding for values of type {!parameters}. *)
val parameters_encoding : parameters Data_encoding.t

(** Slot header representation for the data-availability layer.

    {1 Overview}

    For the data-availability layer, the L1 provides a list of slots
   at every level. A slot is a blob of data that can be interpreted by
   the users of the data-availability layer (such as SCORU).

    The purpose of the data-availability layer is to increase the
   bandwidth of the layer 1 thanks to the distribution of "slots". A
   slot is never posted directly onto the layer 1 blocks but on the
   data-availability layer. The producer of a slot still has to post a
   slot header onto the layer 1. A slot header is an abstract datatype
   certifying that the corresponding slot has some maximum size
   (provided by the layer 1). In other words, the whole data contained
   into the slot cannot exceed some fixed size. This is to avoid
   attacks where a slot header would be posted onto the layer 1 block,
   declared available by the protocol, but actually the slot size
   would be too large to be refuted a posteriori.

   The slot header can also be used to prove that a blob of data is a
   portion of the initial slot. *)

module Commitment : sig
  (** A slot commitment is provided via the environment. *)
  type t = Dal.commitment

  val encoding : t Data_encoding.t

  (** A dummy value for a commitment. This commitment does not
     correspond to any valid pre-image. *)
  val zero : t

  (** Attempt to convert the input representing a commitment encoded as a b58
      string. *)
  val of_b58check_opt : string -> t option
end

module Commitment_proof : sig
  (** A slot commitment proof is provided via the environment. *)
  type t = Dal.commitment_proof

  val encoding : t Data_encoding.t

  (** A dummy value for a commitment proof. *)
  val zero : t
end

module Header : sig
  (** For Layer-1, a slot is identified by the level at which it is published
      and the slot's index. *)
  type id = {published_level : Raw_level_repr.t; index : Dal_slot_index_repr.t}

  (** For Layer-1, a slot is described by its slot {!type-id} and the
     slot's commitment. *)
  type t = {id : id; commitment : Commitment.t}

  (** encoding for values of type {!type-id}. *)
  val id_encoding : id Data_encoding.t

  (** encoding for values of type {!t}. *)
  val encoding : t Data_encoding.t

  (** pretty-printer for values of type {!type-id}. *)
  val pp_id : Format.formatter -> id -> unit

  (** pretty-printer for values of type {!t}. *)
  val pp : Format.formatter -> t -> unit

  (** equal function for values of type {!t}. *)
  val equal : t -> t -> bool

  (** [verify_commitment cryptobox commitment commitment_proof] check
     that for the given commitment, the commitment proof is correct
     using the [cryptbox] primitives. *)
  val verify_commitment :
    Dal.t -> Commitment.t -> Commitment_proof.t -> bool tzresult
end

(** A DAL slot is decomposed to a successive list of pages with fixed content
   size. The size is chosen so that it's possible to inject a page in a Tezos
   L1 operation if needed during the proof phase of a refutation game.
*)
module Page : sig
  type content = Bytes.t

  type slot_index = Dal_slot_index_repr.t

  val pages_per_slot : Dal.parameters -> int

  module Index : sig
    type t = int

    val zero : int

    val encoding : int Data_encoding.t

    val pp : Format.formatter -> int -> unit

    val compare : int -> int -> int

    val equal : int -> int -> bool

    type error += Invalid_page_index of {given : int; min : int; max : int}

    (** [is_in_range ~number_of_pages page_id] returns true if and only if the
      provided [page_id] is within the bounds of allowed pages. *)
    val check_is_in_range : number_of_pages:int -> int -> unit tzresult
  end

  (** Encoding for page contents. *)
  val content_encoding : content Data_encoding.t

  (** A page is identified by its slot ID and by its own index in the list
     of pages of the slot. *)
  type t = {slot_id : Header.id; page_index : Index.t}

  type proof = Dal.page_proof

  (** equal function for values of type {!t}. *)
  val equal : t -> t -> bool

  (** encoding for values of type {!t}. *)
  val encoding : t Data_encoding.t

  (** encoding for values of type {!proof}. *)
  val proof_encoding : proof Data_encoding.t

  (** pretty-printer for values of type {!t}. *)
  val pp : Format.formatter -> t -> unit
end

(** Only one slot header is accepted per slot index. If two slots
   headers are included into a block, the second one will fail.

   Consequently, we rely on the order of operations which is done
   thanks to the fee market.

  This is encapsulated in the following module.  *)
module Slot_market : sig
  (** Represent the fee market for a list of slots. *)
  type t

  (** [init ~length] encodes a list of [length] slots without
     candidates. *)
  val init : length:int -> t

  (** [length t] returns the [length] provided at initialisation time
     (see {!val:init}). *)
  val length : t -> int

  (** [register t index fees] updates the candidate associated to
     index [index]. Returns [Some (_, true)] if the candidate is
     registered. Returns [Some (_, false)] otherwise. Returns [None]
     if the [index] is not in the interval [0;length] where [length]
     is the value provided to the [init] function. *)
  val register : t -> Header.t -> (t * bool) option

  (** [candidates t] returns a list of slot header candidates. *)
  val candidates : t -> Header.t list
end

(** This module provides an abstract data structure (type {!History.t}) that
    represents a skip list used to store successive DAL slots confirmed/attested
    on L1. There is one slot per cell in the skip list. The slots are sorted in
    increasing order by level, and by slot index, for the slots of the same
    level.

    This module also defines a bounded history cache (type
    {!type-History.History_cache.t}) that allows to remember recent values of a
    skip list of type {!History.t} (indexed by the skip lists' hashes). This
    structure is meant to be maintained and used by the rollup node to produce
    refutation proofs involving DAL slot inputs.

    Note on terminology: "confirmed slot" is another name for "attested slot".
*)
module History : sig
  (** Abstract representation of a skip list specialized for
       confirmed slot headers. *)
  type t

  module Pointer_hash : S.HASH

  (** Type of hashes of history. *)
  type hash = Pointer_hash.t

  (** Encoding of the datatype. *)
  val encoding : t Data_encoding.t

  (** The genesis skip list that contains one dummy cell. This cell has
      {!Raw_level_repr.root} as published level and no attested slots. Since Dal
      is not necessarily activated in the genesis block (e.g. this will be the case
      on mainnet), the skip list is reset at the first call to
      {!add_confirmed_slot_headers} to enforce the invariant that there are no gaps
      in the levels of the cells of the skip list.

      So, a skip list is initialized with this genesis cell. It's then replaced
      with a growing (non-dummy) skip list as soon as a call to
      {!add_confirmed_slot_headers} with a level bigger than
      {!Raw_level_repr.root} is performed. This allows to activate Dal at any
      level and having a contiguous skip list (w.r.t. L1 levels). This
      representation allows to produce simpler proofs with a bounded history
      cache. *)
  val genesis : t

  (** Returns the hash of an history. *)
  val hash : t -> hash

  (** The [History_cache.t] structure is basically a bounded lookup table of
      {!t} skip lists. (See {!Bounded_history_repr.S}). In the L1 layer, the
      capacity (bound) is set to zero (nothing is remembered). By contrast,
      the rollup node uses a history cache with a (sufficiently) large capacity
      to participate in all potential refutation games occurring during the
      challenge period. Indeed, the successive recent skip-lists stored in
      the cache are needed to produce proofs involving slots' pages. *)
  module History_cache :
    Bounded_history_repr.S with type key = hash and type value = t

  (** [add_confirmed_slots hist cache published_level ~number_of_slots
      slot_headers] updates the given structure [hist] with the list of
      [slot_headers]. The given [cache] is also updated to add successive values
      of [cell] to it.


      This function checks the following pre-conditions before updating the
      list:

      - The given [published_level] should match all the levels of the slots in
      [slot_headers], if any;

      - [published_level] is the successor the last inserted cell's level.

      - [slot_headers] is sorted in increasing order w.r.t. slots indices.
  *)
  val add_confirmed_slot_headers :
    t ->
    History_cache.t ->
    Raw_level_repr.t ->
    number_of_slots:int ->
    Header.t list ->
    (t * History_cache.t) tzresult

  (** Similiar to {!add_confirmed_slot_headers}, but no cache is provided or
      updated. *)
  val add_confirmed_slot_headers_no_cache :
    t -> Raw_level_repr.t -> number_of_slots:int -> Header.t list -> t tzresult

  (** [equal a b] returns true iff a is equal to b. *)
  val equal : t -> t -> bool

  val pp : Format.formatter -> t -> unit

  (** {1 Dal slots/pages proofs} *)

  (** When a SCORU kernel's inputs come from the DAL, they are provided as
      pages' content for confirmed slots, or None in case the slot doesn't
      exist or is not confirmed.

      In a refutation game involving an import tick of a Dal page input, a
      honest user should be able to provide:

      - When the PVM is requesting a page of a confirmed slot: a proof that the
      slot is confirmed, in addition to needed information to check that the
      page (whose id and content are given) is part of the slot;

      - When the opponent pretends that the PVM is requesting a page of some
      unconfirmed slot, but that slot is not published or not confirmed on L1:
      a proof that the slot (whose id is given via the page's id) cannot be
      confirmed on L1.

      See the documentation in the ml file for more technical details. *)
  type proof

  (** Encoding for {!proof}. *)
  val proof_encoding : proof Data_encoding.t

  (** Pretty-printer for {!proof}. If [serialized] is [false] it will print
      the abstracted proof representation, otherwise if it's [true] it will
      print the serialized version of the proof (i.e. a sequence of bytes). *)
  val pp_proof : serialized:bool -> Format.formatter -> proof -> unit

  (** [produce_proof dal_parameters page_id page_info ~get_history slots_hist]
      produces a proof that either:
      - there exists a confirmed slot in the skip list that contains
        the page identified by [page_id] whose data and slot inclusion proof
        are given by [page_info], or
      - there cannot exist a confirmed slot in the skip list (whose head is
        given by [slots_hist]) containing the page identified by [page_id].

      In the first case above, [page_info] should contain the page's content
      and the proof that the page is part of the (confirmed) slot whose
      id is given in [page_id]. In the second case, no page content or proof
      should be provided, as they are not needed to construct a non-confirmation
      proof.

      The function returns an error in case the slot is not confirmed but the
      page's content and proof are given. It also fails if the slot is confirmed
      but no or bad information about the page are provided.

      Note that, in case the level of the page is far in the past (the Dal skip
      list was not populated yet or the slots of the associated level are not
      valid anymore) should be handled by the caller.

      [dal_parameters] is used when verifying that/if the page is part of
      the candidate slot (if any).
  *)
  val produce_proof :
    parameters ->
    Page.t ->
    page_info:(Page.content * Page.proof) option ->
    get_history:(hash -> t option Lwt.t) ->
    t ->
    (proof * Page.content option) tzresult Lwt.t

  (** [verify_proof dal_params page_id snapshot proof] verifies that the given
      [proof] is a valid proof to show that either:
      - the page identified by [page_id] belongs to a confirmed slot stored in
      the skip list whose head is [snapshot], or
      - there is not confirmed slot in the skip list (whose head is) [snapshot]
      that could contain the page identified by [page_id].

      [dal_parameters] is used when verifying that/if the page is part of
      the candidate slot (if any).
  *)
  val verify_proof :
    parameters -> Page.t -> t -> proof -> Page.content option tzresult

  type error += Add_element_in_slots_skip_list_violates_ordering

  type error +=
    | Dal_proof_error of string
    | Unexpected_page_size of {expected_size : int; page_size : int}

  module Internal_for_tests : sig
    (** The content of a cell in the DAL skip list. We don't store the slot
        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_content = Unattested of Header.id | Attested of Header.t

    (** Returns the content of the last cell in the given skip list. *)
    val content : t -> cell_content

    (** [proof_statement_is serialized_proof expected] will return [true] if
        the deserialized proof and the [expected] proof shape match and [false]
        otherwise.
        Note that it will also return [false] if deserialization fails.  *)
    val proof_statement_is : proof -> [`Confirmed | `Unconfirmed] -> bool
  end
end
back to top