https://gitlab.com/tezos/tezos
Raw File
Tip revision: c2b042ff1b4e0b3bcc28d84975bcfb32364e5593 authored by saroupille on 24 March 2024, 22:11:11 UTC
Debug
Tip revision: c2b042f
baking_highwatermarks.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

open Protocol_client_context
open Protocol.Alpha_context

type highwatermark = {round : Round.t; level : int32}

let highwatermark_encoding : highwatermark Data_encoding.t =
  let open Data_encoding in
  conv
    (fun {round; level} -> (round, level))
    (fun (round, level) -> {round; level})
    (obj2
       (req "round" Protocol.Alpha_context.Round.encoding)
       (req "level" int32))

let pp_highwatermark fmt {round; level} =
  Format.fprintf fmt "level: %ld, round: %a" level Round.pp round

type error += Block_previously_baked of highwatermark

type error += Block_previously_preattested of highwatermark

type error += Block_previously_attested of highwatermark

let () =
  register_error_kind
    `Permanent
    ~id:"highwatermarks.block_previously_baked"
    ~title:"Block previously baked"
    ~description:"Trying to bake a block at a level previously baked"
    ~pp:(fun ppf highwatermark ->
      Format.fprintf
        ppf
        "A block with a higher watermark than the current one (%a) was \
         previously baked."
        pp_highwatermark
        highwatermark)
    highwatermark_encoding
    (function
      | Block_previously_baked highwatermark -> Some highwatermark | _ -> None)
    (fun highwatermark -> Block_previously_baked highwatermark) ;
  register_error_kind
    `Permanent
    ~id:"highwatermarks.block_previously_preattested"
    ~title:"Block previously preattested"
    ~description:"Trying to preattest a block at a level previously preattested"
    ~pp:(fun ppf highwatermark ->
      Format.fprintf
        ppf
        "A preattestation with a higher watermark than the current one (%a) \
         was already produced."
        pp_highwatermark
        highwatermark)
    highwatermark_encoding
    (function
      | Block_previously_preattested highwatermark -> Some highwatermark
      | _ -> None)
    (fun highwatermark -> Block_previously_preattested highwatermark) ;
  register_error_kind
    `Permanent
    ~id:"highwatermarks.block_previously_attested"
    ~title:"Block previously attested"
    ~description:"Trying to attest a block at a level previously attested"
    ~pp:(fun ppf highwatermark ->
      Format.fprintf
        ppf
        "An attestation with a higher watermark than the current one (%a) was \
         already produced."
        pp_highwatermark
        highwatermark)
    highwatermark_encoding
    (function
      | Block_previously_attested highwatermark -> Some highwatermark
      | _ -> None)
    (fun highwatermark -> Block_previously_attested highwatermark)

module DelegateMap = Map.Make (struct
  type t = Signature.Public_key_hash.t

  let compare = Signature.Public_key_hash.compare
end)

let highwatermark_delegate_map_encoding =
  let open Data_encoding in
  conv
    DelegateMap.bindings
    DelegateMap.(
      fun l -> List.fold_left (fun map (k, v) -> add k v map) empty l)
    (list
       (obj2
          (req "delegate" Signature.Public_key_hash.encoding)
          (req "highwatermark" highwatermark_encoding)))

type highwatermarks = {
  blocks : highwatermark DelegateMap.t;
  preattestations : highwatermark DelegateMap.t;
  attestations : highwatermark DelegateMap.t;
}

type t = highwatermarks

let encoding =
  let open Data_encoding in
  conv
    (fun {blocks; preattestations; attestations} ->
      (blocks, preattestations, attestations))
    (fun (blocks, preattestations, attestations) ->
      {blocks; preattestations; attestations})
    (obj3
       (req "blocks" highwatermark_delegate_map_encoding)
       (req "preattestations" highwatermark_delegate_map_encoding)
       (req "attestations" highwatermark_delegate_map_encoding))

let empty =
  {
    blocks = DelegateMap.empty;
    preattestations = DelegateMap.empty;
    attestations = DelegateMap.empty;
  }

(* We do not lock these functions. The caller will be already locked. *)
let load (cctxt : #Protocol_client_context.full) location : t tzresult Lwt.t =
  protect (fun () ->
      cctxt#load (Baking_files.filename location) encoding ~default:empty)

let save_highwatermarks (cctxt : #Protocol_client_context.full) filename
    highwatermarks : unit tzresult Lwt.t =
  protect (fun () ->
      (* TODO: improve the backend so we don't write partial informations *)
      cctxt#write filename highwatermarks encoding)

let may_sign highwatermarks ~delegate ~level ~round =
  match DelegateMap.find delegate highwatermarks with
  | None -> true
  | Some highwatermark ->
      if Compare.Int32.(highwatermark.level < level) then true
      else if Compare.Int32.(highwatermark.level = level) then
        Round.(highwatermark.round < round)
      else false

let may_sign_block cctxt (location : [`Highwatermarks] Baking_files.location)
    ~delegate ~level ~round =
  let open Lwt_result_syntax in
  let* all_highwatermarks = load cctxt location in
  return @@ may_sign all_highwatermarks.blocks ~delegate ~level ~round

let may_sign_preattestation all_highwatermarks ~delegate ~level ~round =
  may_sign all_highwatermarks.preattestations ~delegate ~level ~round

let may_sign_attestation all_highwatermarks ~delegate ~level ~round =
  may_sign all_highwatermarks.attestations ~delegate ~level ~round

let record map ~delegate ~new_level ~new_round =
  DelegateMap.update
    delegate
    (function
      | None -> Some {level = new_level; round = new_round}
      | Some ({level; round} as prev) ->
          if Compare.Int32.(new_level > level) then
            Some {level = new_level; round = new_round}
          else if Compare.Int32.(new_level = level) then
            if Round.(new_round > round) then
              Some {level = new_level; round = new_round}
            else Some prev
          else Some prev)
    map

let record_block (cctxt : #Protocol_client_context.full) location ~delegate
    ~level ~round =
  let open Lwt_result_syntax in
  let filename = Baking_files.filename location in
  let* highwatermarks = load cctxt location in
  let new_blocks =
    record highwatermarks.blocks ~delegate ~new_level:level ~new_round:round
  in
  save_highwatermarks cctxt filename {highwatermarks with blocks = new_blocks}

let record_preattestation (cctxt : #Protocol_client_context.full) location
    ~delegate ~level ~round =
  let open Lwt_result_syntax in
  let filename = Baking_files.filename location in
  let* highwatermarks = load cctxt location in
  let new_preattestations =
    record
      highwatermarks.preattestations
      ~delegate
      ~new_level:level
      ~new_round:round
  in
  save_highwatermarks
    cctxt
    filename
    {highwatermarks with preattestations = new_preattestations}

let record_attestation (cctxt : #Protocol_client_context.full) location
    ~delegate ~level ~round =
  let open Lwt_result_syntax in
  let filename = Baking_files.filename location in
  let* highwatermarks = load cctxt location in
  let new_attestations =
    record
      highwatermarks.attestations
      ~delegate
      ~new_level:level
      ~new_round:round
  in
  save_highwatermarks
    cctxt
    filename
    {highwatermarks with attestations = new_attestations}

let record_all_preattestations all_highwatermarks cctxt location ~delegates
    ~level ~round =
  let new_preattestations =
    List.fold_left
      (fun map delegate ->
        record map ~delegate ~new_level:level ~new_round:round)
      all_highwatermarks.preattestations
      delegates
  in
  let filename = Baking_files.filename location in
  save_highwatermarks
    cctxt
    filename
    {all_highwatermarks with preattestations = new_preattestations}

let record_all_attestations all_highwatermarks cctxt location ~delegates ~level
    ~round =
  let new_attestations =
    List.fold_left
      (fun map delegate ->
        record map ~delegate ~new_level:level ~new_round:round)
      all_highwatermarks.attestations
      delegates
  in
  let filename = Baking_files.filename location in
  save_highwatermarks
    cctxt
    filename
    {all_highwatermarks with attestations = new_attestations}
back to top