(*****************************************************************************)
(*                                                                           *)
(* 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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

(***************** Prevalidation errors ***********************************)

type error += Parse_error
type error += Too_many_operations
type error += Oversized_operation of { size: int ; max: int }
type error += Future_block_header of { block: Block_hash.t ; block_time : Time.t ; time : Time.t }

let () =
  (* Parse error *)
  register_error_kind
    `Permanent
    ~id:"node.prevalidation.parse_error"
    ~title:"Parsing error in prevalidation"
    ~description:"Raised when an operation has not been parsed correctly during prevalidation."
    ~pp:(fun ppf () ->
        Format.fprintf ppf "Operation parsing error in prevalidation.")
    Data_encoding.empty
    (function Parse_error -> Some () | _ -> None)
    (fun () -> Parse_error) ;
  (* Too many operations *)
  register_error_kind
    `Temporary
    ~id:"node.prevalidation.too_many_operations"
    ~title:"Too many pending operations in prevalidation"
    ~description:"The prevalidation context is full."
    ~pp:(fun ppf () ->
        Format.fprintf ppf "Too many operations in prevalidation context.")
    Data_encoding.empty
    (function Too_many_operations -> Some () | _ -> None)
    (fun () -> Too_many_operations) ;
  (* Oversized operation *)
  register_error_kind
    `Permanent
    ~id:"node.prevalidation.oversized_operation"
    ~title:"Oversized operation"
    ~description:"The operation size is bigger than allowed."
    ~pp:(fun ppf (size, max) ->
        Format.fprintf ppf "Oversized operation (size: %d, max: %d)"
          size max)
    Data_encoding.(obj2
                     (req "size" int31)
                     (req "max_size" int31))
    (function Oversized_operation { size ; max } -> Some (size, max) | _ -> None)
    (fun (size, max) -> Oversized_operation { size ; max }) ;
  (* Block from the future *)
  register_error_kind
    `Temporary
    ~id:"node.prevalidation.future_block_header"
    ~title:"Future block header"
    ~description:"The block was annotated with a time too far in the future."
    ~pp:(fun ppf (block, block_time, time) ->
        Format.fprintf ppf "Future block header (block: %a, block_time: %a, time: %a)"
          Block_hash.pp block Time.pp_hum block_time Time.pp_hum time)
    Data_encoding.(obj3
                     (req "block" Block_hash.encoding)
                     (req "block_time" Time.encoding)
                     (req "time" Time.encoding))
    (function Future_block_header { block ; block_time ; time } -> Some (block, block_time, time) | _ -> None)
    (fun (block, block_time, time) -> Future_block_header { block ; block_time ; time })


(************************* State errors ***********************************)

type error +=  Unknown_chain of Chain_id.t

type error += Bad_data_dir

type error += Block_not_invalid of Block_hash.t

let () =
  (* Unknown network *)
  register_error_kind
    `Permanent
    ~id:"node.state.unknown_chain"
    ~title:"Unknown chain"
    ~description:"The chain identifier could not be found in \
                  the chain identifiers table."
    ~pp:(fun ppf id ->
        Format.fprintf ppf "Unknown chain %a" Chain_id.pp id)
    Data_encoding.(obj1 (req "chain" Chain_id.encoding))
    (function Unknown_chain x -> Some x | _ -> None)
    (fun x -> Unknown_chain x) ;
  register_error_kind
    `Permanent
    ~id:"node.state.bad_data_dir"
    ~title:"Bad data directory"
    ~description:"The data directory could not be read. \
                  This could be because it was generated with an \
                  old version of the tezos-node program. \
                  Deleting and regenerating this directory \
                  may fix the problem."
    ~pp:(fun ppf () -> Format.fprintf ppf "Bad data directory.")
    Data_encoding.empty
    (function Bad_data_dir -> Some () | _ -> None)
    (fun () -> Bad_data_dir) ;
  (* Block not invalid *)
  register_error_kind
    `Permanent
    ~id:"node.state.block_not_invalid"
    ~title:"Block not invalid"
    ~description:"The invalid block to be unmarked was not actually invalid."
    ~pp:(fun ppf block ->
        Format.fprintf ppf "Block %a was expected to be invalid, but was not actually invalid."
          Block_hash.pp block)
    Data_encoding.(obj1 (req "block" Block_hash.encoding))
    (function Block_not_invalid block -> Some block | _ -> None)
    (fun block -> Block_not_invalid block)

(* Block database error *)

type error += Inconsistent_hash of Context_hash.t * Context_hash.t

let () =
  (* Inconsistent hash *)
  register_error_kind
    `Permanent
    ~id:"node.state.block.inconsistent_context_hash"
    ~title:"Inconsistent commit hash"
    ~description:
      "When commiting the context of a block, the announced context \
       hash was not the one computed at commit time."
    ~pp: (fun ppf (got, exp) ->
        Format.fprintf ppf
          "@[<v 2>Inconsistant hash:@ got: %a@ expected: %a"
          Context_hash.pp got
          Context_hash.pp exp)
    Data_encoding.(obj2
                     (req "wrong_context_hash" Context_hash.encoding)
                     (req "expected_context_hash" Context_hash.encoding))
    (function Inconsistent_hash (got, exp) -> Some (got, exp) | _ -> None)
    (fun (got, exp) -> Inconsistent_hash (got, exp))

(******************* Bootstrap pipeline errors ****************************)

type error += Invalid_locator of P2p_peer.Id.t * Block_locator.t

let () =
  (* Invalid locator *)
  register_error_kind
    `Permanent
    ~id:"node.bootstrap_pipeline.invalid_locator"
    ~title:"Invalid block locator"
    ~description:"Block locator is invalid."
    ~pp: (fun ppf (id, locator) ->
        Format.fprintf ppf
          "Invalid block locator on peer %a:\n%a"
          P2p_peer.Id.pp id
          Block_locator.pp locator)
    Data_encoding.(obj2
                     (req "id" P2p_peer.Id.encoding)
                     (req "locator" Block_locator.encoding))
    (function | Invalid_locator (id, loc) -> Some (id, loc) | _ -> None)
    (fun (id, loc) -> Invalid_locator (id, loc))

(******************* Protocol validator errors ****************************)

type protocol_error =
  | Compilation_failed
  | Dynlinking_failed

type error += Invalid_protocol of { hash: Protocol_hash.t ; error: protocol_error }

let protocol_error_encoding =
  let open Data_encoding in
  union
    [
      case (Tag 0)
        ~title:"Compilation failed"
        (obj1
           (req "error" (constant "compilation_failed")))
        (function Compilation_failed -> Some ()
                | _ -> None)
        (fun () -> Compilation_failed) ;
      case (Tag 1)
        ~title:"Dynlinking failed"
        (obj1
           (req "error" (constant "dynlinking_failed")))
        (function Dynlinking_failed -> Some ()
                | _ -> None)
        (fun () -> Dynlinking_failed) ;
    ]

let pp_protocol_error ppf = function
  | Compilation_failed ->
      Format.fprintf ppf "compilation error"
  | Dynlinking_failed ->
      Format.fprintf ppf "dynlinking error"

let () =
  (* Invalid protocol *)
  register_error_kind
    `Permanent
    ~id:"node.protocol_validator.invalid_protocol"
    ~title:"Invalid protocol"
    ~description:"Invalid protocol."
    ~pp:begin fun ppf (protocol, error) ->
      Format.fprintf ppf
        "@[<v 2>Invalid protocol %a@ %a@]"
        Protocol_hash.pp_short protocol pp_protocol_error error
    end
    Data_encoding.(merge_objs
                     (obj1 (req "invalid_protocol" Protocol_hash.encoding))
                     protocol_error_encoding)
    (function Invalid_protocol { hash ; error } ->
       Some (hash, error) | _ -> None)
    (fun (hash, error) ->
       Invalid_protocol { hash ; error })

(********************* Peer validator errors ******************************)

type error +=
  | Unknown_ancestor
  | Known_invalid

let () =
  (* Unknown ancestor *)
  register_error_kind
    `Permanent
    ~id: "node.peer_validator.unknown_ancestor"
    ~title: "Unknown ancestor"
    ~description: "Unknown ancestor block found in the peer's chain"
    ~pp: (fun ppf () -> Format.fprintf ppf "Unknown ancestor")
    Data_encoding.empty
    (function Unknown_ancestor -> Some () | _ -> None)
    (fun () -> Unknown_ancestor) ;
  (* Known invalid *)
  register_error_kind
    `Permanent
    ~id: "node.peer_validator.known_invalid"
    ~title: "Known invalid"
    ~description: "Known invalid block found in the peer's chain"
    ~pp: (fun ppf () -> Format.fprintf ppf "Known invalid")
    Data_encoding.empty
    (function Known_invalid -> Some () | _ -> None)
    (fun () -> Known_invalid)

(************************ Validator errors ********************************)

type error +=  Inactive_chain of Chain_id.t
type error += Checkpoint_error of Block_hash.t * P2p_peer.Id.t option

let () =
  (* Inactive network *)
  register_error_kind
    `Branch
    ~id: "node.validator.inactive_chain"
    ~title: "Inactive chain"
    ~description: "Attempted validation of a block from an inactive chain."
    ~pp: (fun ppf chain ->
        Format.fprintf ppf
          "Tried to validate a block from chain %a, \
           that is not currently considered active."
          Chain_id.pp chain)
    Data_encoding.(obj1 (req "inactive_chain" Chain_id.encoding))
    (function Inactive_chain chain -> Some chain | _ -> None)
    (fun chain -> Inactive_chain chain) ;
  register_error_kind
    `Branch
    ~id:"node.validator.checkpoint_error"
    ~title: "Block incompatble with the current checkpoint."
    ~description: "The block belongs to a branch that is not compatible \
                   with the current checkpoint."
    ~pp: (fun ppf (block, peer) ->
        match peer with
        | None ->
            Format.fprintf ppf
              "The block %a is incompatible with the current checkpoint."
              Block_hash.pp_short block
        | Some peer ->
            Format.fprintf ppf
              "The peer %a send us a block which is a sibling \
               of the configured checkpoint (%a)."
              P2p_peer.Id.pp peer
              Block_hash.pp_short block)
    Data_encoding.(obj2
                     (req "block" Block_hash.encoding)
                     (opt "peer" P2p_peer.Id.encoding))
    (function Checkpoint_error (block, peer) -> Some (block, peer) | _ -> None)
    (fun (block, peer) -> Checkpoint_error (block, peer))

