Raw File
ticket_storage.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2021 Trili Tech, <contact@trili.tech>                       *)
(*                                                                           *)
(* 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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

type error +=
  | Negative_ticket_balance of {key : Ticket_hash_repr.t; balance : Z.t}
  | Used_storage_space_underflow

let () =
  let open Data_encoding in
  register_error_kind
    `Permanent
    ~id:"Negative_ticket_balance"
    ~title:"Negative ticket balance"
    ~description:"Attempted to set a negative ticket balance value"
    ~pp:(fun ppf (key, balance) ->
      Format.fprintf
        ppf
        "Attempted to set negative ticket balance value '%a' for key %a."
        Z.pp_print
        balance
        Ticket_hash_repr.pp
        key)
    (obj2 (req "key" Ticket_hash_repr.encoding) (req "balance" Data_encoding.z))
    (function
      | Negative_ticket_balance {key; balance} -> Some (key, balance)
      | _ -> None)
    (fun (key, balance) -> Negative_ticket_balance {key; balance}) ;
  register_error_kind
    `Permanent
    ~id:"Used_storage_underflow"
    ~title:"Ticket balance used storage underflow"
    ~description:
      "Attempt to free more bytes than allocated for the tickets balance"
    empty
    (function Used_storage_space_underflow -> Some () | _ -> None)
    (fun () -> Used_storage_space_underflow)

let get_balance ctxt key =
  Storage.Ticket_balance.Table.find ctxt key >|=? fun (ctxt, res) -> (res, ctxt)

let set_balance ctxt key balance =
  let cost_of_key = Z.of_int 65 in
  fail_when
    Compare.Z.(balance < Z.zero)
    (Negative_ticket_balance {key; balance})
  >>=? fun () ->
  if Compare.Z.(balance = Z.zero) then
    Storage.Ticket_balance.Table.remove ctxt key
    >|=? fun (ctxt, freed, existed) ->
    (* If we remove an existing entry, then we return the freed size for
       both the key and the value. *)
    let freed =
      if existed then Z.neg @@ Z.add cost_of_key (Z.of_int freed) else Z.zero
    in
    (freed, ctxt)
  else
    Storage.Ticket_balance.Table.add ctxt key balance
    >|=? fun (ctxt, size_diff, existed) ->
    let size_diff =
      let z_diff = Z.of_int size_diff in
      (* For a new entry we also charge the space for storing the key *)
      if existed then z_diff else Z.add cost_of_key z_diff
    in
    (size_diff, ctxt)

let adjust_balance ctxt key ~delta =
  get_balance ctxt key >>=? fun (res, ctxt) ->
  let old_balance = Option.value ~default:Z.zero res in
  set_balance ctxt key (Z.add old_balance delta)

let adjust_storage_space ctxt ~storage_diff =
  if Compare.Z.(storage_diff = Z.zero) then return (Z.zero, ctxt)
  else
    Storage.Ticket_balance.Used_storage_space.find ctxt >>=? fun used_storage ->
    let used_storage = Option.value ~default:Z.zero used_storage in
    Storage.Ticket_balance.Paid_storage_space.find ctxt >>=? fun paid_storage ->
    let paid_storage = Option.value ~default:Z.zero paid_storage in
    let new_used_storage = Z.add used_storage storage_diff in
    error_when
      Compare.Z.(new_used_storage < Z.zero)
      Used_storage_space_underflow
    >>?= fun () ->
    Storage.Ticket_balance.Used_storage_space.add ctxt new_used_storage
    >>= fun ctxt ->
    let diff = Z.sub new_used_storage paid_storage in
    if Compare.Z.(Z.zero < diff) then
      Storage.Ticket_balance.Paid_storage_space.add ctxt new_used_storage
      >>= fun ctxt -> return (diff, ctxt)
    else return (Z.zero, ctxt)

module Internal_for_tests = struct
  let used_storage_space c =
    Storage.Ticket_balance.Used_storage_space.find c
    >|=? Option.value ~default:Z.zero

  let paid_storage_space c =
    Storage.Ticket_balance.Paid_storage_space.find c
    >|=? Option.value ~default:Z.zero
end
back to top