Revision d8f4b9d21f954c3fa150d8ad8f715375e15ef532 authored by KOBAYASHI Kazuhiro on 20 April 2023, 08:01:02 UTC, committed by Marge Bot on 13 June 2023, 11:56:25 UTC
1 parent 98fbc20
Raw File
block_validation.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com>     *)
(* Copyright (c) 2018-2021 Nomadic Labs. <contact@nomadic-labs.com>          *)
(* Copyright (c) 2020 Metastate AG <hello@metastate.dev>                     *)
(*                                                                           *)
(* 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 Block_validator_errors
open Validation_errors

module Event = struct
  include Internal_event.Simple

  let inherited_inconsistent_cache =
    declare_1
      ~section:["block"; "validation"]
      ~name:"block_validation_inconsistent_cache"
      ~msg:"applied block {hash} with an inconsistent cache: reloading cache"
      ~level:Warning
      ~pp1:Block_hash.pp
      ("hash", Block_hash.encoding)
end

type validation_store = {
  resulting_context_hash : Context_hash.t;
  timestamp : Time.Protocol.t;
  message : string option;
  max_operations_ttl : int;
  last_allowed_fork_level : Int32.t;
}

let validation_store_encoding =
  let open Data_encoding in
  conv
    (fun {
           resulting_context_hash;
           timestamp;
           message;
           max_operations_ttl;
           last_allowed_fork_level;
         } ->
      ( resulting_context_hash,
        timestamp,
        message,
        max_operations_ttl,
        last_allowed_fork_level ))
    (fun ( resulting_context_hash,
           timestamp,
           message,
           max_operations_ttl,
           last_allowed_fork_level ) ->
      {
        resulting_context_hash;
        timestamp;
        message;
        max_operations_ttl;
        last_allowed_fork_level;
      })
    (obj5
       (req "resulting_context_hash" Context_hash.encoding)
       (req "timestamp" Time.Protocol.encoding)
       (req "message" (option string))
       (req "max_operations_ttl" int31)
       (req "last_allowed_fork_level" int32))

type operation_metadata = Metadata of Bytes.t | Too_large_metadata

let operation_metadata_equal m1 m2 =
  match (m1, m2) with
  | Metadata b1, Metadata b2 -> Bytes.equal b1 b2
  | Too_large_metadata, Too_large_metadata -> true
  | Metadata _, Too_large_metadata | Too_large_metadata, Metadata _ -> false

let operation_metadata_encoding =
  let open Data_encoding in
  def
    "operation_metadata"
    ~title:"operation_metadata"
    ~description:"An operation metadata"
    (union
       ~tag_size:`Uint8
       [
         case
           ~title:"metadata"
           ~description:"Content of an operation's metadata."
           (Tag 0)
           bytes
           (function Metadata bytes -> Some bytes | _ -> None)
           (fun bytes -> Metadata bytes);
         case
           ~title:"too_large_metadata"
           ~description:
             "Nothing, as this operation metadata was declared to be too large \
              to be considered."
           (Tag 1)
           unit
           (function Too_large_metadata -> Some () | _ -> None)
           (fun () -> Too_large_metadata);
       ])

type ops_metadata =
  | No_metadata_hash of operation_metadata list list
  | Metadata_hash of (operation_metadata * Operation_metadata_hash.t) list list

module Shell_header_hash =
  Blake2B.Make
    (Base58)
    (struct
      let name = "shell_header_hash"

      let title = "A shell header identifier"

      let b58check_prefix = Base58.Prefix.block_hash

      let size = None
    end)

let hash_shell_header shell =
  Shell_header_hash.hash_bytes
    [Data_encoding.Binary.to_bytes_exn Block_header.shell_header_encoding shell]

type result = {
  shell_header_hash : Shell_header_hash.t;
  validation_store : validation_store;
  block_metadata : Bytes.t * Block_metadata_hash.t option;
  ops_metadata : ops_metadata;
}

type apply_result = {
  result : result;
  cache : Tezos_protocol_environment.Context.cache;
}

let check_proto_environment_version_increasing block_hash before after =
  let open Result_syntax in
  if Protocol.compare_version before after <= 0 then return_unit
  else
    tzfail
      (invalid_block
         block_hash
         (Invalid_protocol_environment_transition (before, after)))

let update_testchain_status ctxt ~predecessor_hash timestamp =
  let open Lwt_syntax in
  let* tc = Context_ops.get_test_chain ctxt in
  match tc with
  | Not_running -> Lwt.return ctxt
  | Running {expiration; _} ->
      if Time.Protocol.(expiration <= timestamp) then
        Context_ops.add_test_chain ctxt Not_running
      else Lwt.return ctxt
  | Forking {protocol; expiration} ->
      let genesis =
        Context_ops.compute_testchain_genesis ctxt predecessor_hash
      in
      let chain_id = Chain_id.of_block_hash genesis in
      (* legacy semantics *)
      Context_ops.add_test_chain
        ctxt
        (Running {chain_id; genesis; protocol; expiration})

let init_test_chain chain_id ctxt forked_header =
  let open Lwt_result_syntax in
  let*! tc = Context_ops.get_test_chain ctxt in
  match tc with
  | Not_running | Running _ -> assert false
  | Forking {protocol; _} ->
      let* (module Proto_test) =
        match Registered_protocol.get protocol with
        | Some proto -> return proto
        | None -> tzfail (Missing_test_protocol protocol)
      in
      let test_ctxt = ctxt in
      let*! () =
        Validation_events.(emit new_protocol_initialisation protocol)
      in
      let* {context = test_ctxt; _} =
        Proto_test.init chain_id test_ctxt forked_header.Block_header.shell
      in
      let*! test_ctxt = Context_ops.add_test_chain test_ctxt Not_running in
      let*! test_ctxt = Context_ops.add_protocol test_ctxt protocol in
      Lwt_result.ok
      @@ Context_ops.commit_test_chain_genesis test_ctxt forked_header

let result_encoding =
  let open Data_encoding in
  let ops_metadata_encoding =
    union
      ~tag_size:`Uint8
      [
        case
          ~title:"no metadata hash"
          (Tag 0)
          (list (list operation_metadata_encoding))
          (function
            | No_metadata_hash ops_metadata -> Some ops_metadata | _ -> None)
          (fun ops_metadata -> No_metadata_hash ops_metadata);
        case
          ~title:"metadata hash"
          (Tag 1)
          (list
             (list
                (tup2
                   operation_metadata_encoding
                   Operation_metadata_hash.encoding)))
          (function
            | Metadata_hash ops_metadata -> Some ops_metadata | _ -> None)
          (fun ops_metadata -> Metadata_hash ops_metadata);
      ]
  in
  conv
    (fun {shell_header_hash; validation_store; block_metadata; ops_metadata} ->
      (shell_header_hash, validation_store, block_metadata, ops_metadata))
    (fun (shell_header_hash, validation_store, block_metadata, ops_metadata) ->
      {shell_header_hash; validation_store; block_metadata; ops_metadata})
    (obj4
       (req "shell_header_hash" Shell_header_hash.encoding)
       (req "validation_store" validation_store_encoding)
       (req "block_metadata" (tup2 bytes (option Block_metadata_hash.encoding)))
       (req "ops_metadata" ops_metadata_encoding))

let preapply_result_encoding :
    (Block_header.shell_header * error Preapply_result.t list) Data_encoding.t =
  let open Data_encoding in
  obj2
    (req "shell_header" Block_header.shell_header_encoding)
    (req
       "preapplied_operations_result"
       (list (Preapply_result.encoding Tezos_rpc.Error.encoding)))

let may_force_protocol_upgrade ~user_activated_upgrades ~level
    (validation_result : Tezos_protocol_environment.validation_result) =
  let open Lwt_syntax in
  match
    Block_header.get_forced_protocol_upgrade ~user_activated_upgrades ~level
  with
  | None -> return validation_result
  | Some hash ->
      let* context =
        Tezos_protocol_environment.Context.set_protocol
          validation_result.context
          hash
      in
      return {validation_result with context}

(** Applies user activated updates based either on block level or on
    voted protocols *)
let may_patch_protocol ~user_activated_upgrades
    ~user_activated_protocol_overrides ~level
    (validation_result : Tezos_protocol_environment.validation_result) =
  let open Lwt_syntax in
  let context = validation_result.context in
  let* protocol = Context_ops.get_protocol context in
  match
    Block_header.get_voted_protocol_overrides
      ~user_activated_protocol_overrides
      protocol
  with
  | None ->
      may_force_protocol_upgrade
        ~user_activated_upgrades
        ~level
        validation_result
  | Some replacement_protocol ->
      let* context =
        Tezos_protocol_environment.Context.set_protocol
          validation_result.context
          replacement_protocol
      in
      return {validation_result with context}

module Make (Proto : Registered_protocol.T) = struct
  type 'operation_data preapplied_operation = {
    hash : Operation_hash.t;
    raw : Operation.t;
    protocol_data : 'operation_data;
  }

  type preapply_state = {
    validation_state : Proto.validation_state;
    application_state : Proto.application_state;
    applied :
      (Proto.operation_data preapplied_operation * Proto.operation_receipt) list;
    live_blocks : Block_hash.Set.t;
    live_operations : Operation_hash.Set.t;
  }

  type preapply_result =
    | Applied of preapply_state * Proto.operation_receipt
    | Branch_delayed of error list
    | Branch_refused of error list
    | Refused of error list
    | Outdated

  let check_block_header ~(predecessor_block_header : Block_header.t)
      ~predecessor_resulting_context_hash hash (block_header : Block_header.t) =
    let open Lwt_result_syntax in
    let* () =
      fail_unless
        (Int32.succ predecessor_block_header.shell.level
        = block_header.shell.level)
        (invalid_block hash
        @@ Invalid_level
             {
               expected = Int32.succ predecessor_block_header.shell.level;
               found = block_header.shell.level;
             })
    in
    let* () =
      fail_unless
        Time.Protocol.(
          predecessor_block_header.shell.timestamp
          < block_header.shell.timestamp)
        (invalid_block hash Non_increasing_timestamp)
    in
    let* () =
      fail_unless
        Fitness.(
          predecessor_block_header.shell.fitness < block_header.shell.fitness)
        (invalid_block hash Non_increasing_fitness)
    in
    let* () =
      fail_unless
        Compare.List_length_with.(
          Proto.validation_passes = block_header.shell.validation_passes)
        (invalid_block
           hash
           (Unexpected_number_of_validation_passes
              block_header.shell.validation_passes))
    in
    let is_consistent =
      match Proto.expected_context_hash with
      | Predecessor_resulting_context ->
          Context_hash.equal
            block_header.shell.context
            predecessor_resulting_context_hash
      | Resulting_context ->
          (* The check that a header's context is the resulting context
             will be performed post-application (when the resulting
             context is known). *)
          true
    in
    let* () =
      fail_unless
        is_consistent
        (Validation_errors.Inconsistent_hash
           (predecessor_resulting_context_hash, block_header.shell.context))
    in
    return_unit

  let parse_block_header block_hash (block_header : Block_header.t) =
    let open Lwt_result_syntax in
    match
      Data_encoding.Binary.of_bytes_opt
        Proto.block_header_data_encoding
        block_header.protocol_data
    with
    | None -> tzfail (invalid_block block_hash Cannot_parse_block_header)
    | Some protocol_data ->
        return
          ({shell = block_header.shell; protocol_data} : Proto.block_header)

  let check_one_operation_quota block_hash pass ops quota =
    let open Lwt_result_syntax in
    let* () =
      fail_unless
        (match quota.Tezos_protocol_environment.max_op with
        | None -> true
        | Some max -> Compare.List_length_with.(ops <= max))
        (let max = Option.value ~default:~-1 quota.max_op in
         invalid_block
           block_hash
           (Too_many_operations {pass; found = List.length ops; max}))
    in
    List.iter_ep
      (fun op ->
        let size = Data_encoding.Binary.length Operation.encoding op in
        fail_unless
          (size <= Proto.max_operation_data_length)
          (invalid_block
             block_hash
             (Oversized_operation
                {
                  operation = Operation.hash op;
                  size;
                  max = Proto.max_operation_data_length;
                })))
      ops

  let check_operation_quota block_hash operations =
    let combined =
      match
        List.combine
          ~when_different_lengths:()
          operations
          Proto.validation_passes
      with
      | Ok combined -> combined
      | Error () ->
          raise (Invalid_argument "Block_validation.check_operation_quota")
    in
    List.iteri_ep
      (fun i (ops, quota) ->
        (* passes are 1-based, iteri is 0-based *)
        let pass = i + 1 in
        check_one_operation_quota block_hash pass ops quota)
      combined

  let parse_operations block_hash operations =
    List.mapi_es
      (fun pass ->
        let open Lwt_result_syntax in
        List.map_es (fun op ->
            let op_hash = Operation.hash op in
            match
              Data_encoding.Binary.of_bytes_opt
                Proto.operation_data_encoding_with_legacy_attestation_name
                op.Operation.proto
            with
            | None ->
                tzfail
                  (invalid_block block_hash (Cannot_parse_operation op_hash))
            | Some protocol_data ->
                let op = {Proto.shell = op.shell; protocol_data} in
                let allowed_pass = Proto.acceptable_pass op in
                let is_pass_consistent =
                  match allowed_pass with
                  | None -> false
                  | Some n -> Int.equal pass n
                in
                let* () =
                  fail_unless
                    is_pass_consistent
                    (invalid_block
                       block_hash
                       (Unallowed_pass {operation = op_hash; pass; allowed_pass}))
                in
                return (op_hash, op)))
      operations

  (* FIXME: This code is used by preapply but emitting time
     measurement events in prevalidation should not impact current
     benchmarks.
     See https://gitlab.com/tezos/tezos/-/issues/2716 *)
  let compute_metadata ~operation_metadata_size_limit proto_env_version
      block_data ops_metadata =
    let open Lwt_result_syntax in
    (* Block and operation metadata hashes are not required for
       environment V0. *)
    let should_include_metadata_hashes =
      match proto_env_version with
      | Protocol.V0 -> false
      | Protocol.(V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10) -> true
    in
    let block_metadata =
      let metadata =
        Data_encoding.Binary.to_bytes_exn
          Proto.block_header_metadata_encoding
          block_data
      in
      let metadata_hash_opt =
        if should_include_metadata_hashes then
          Some (Block_metadata_hash.hash_bytes [metadata])
        else None
      in
      (metadata, metadata_hash_opt)
    in
    let* ops_metadata =
      try[@time.duration_lwt metadata_serde_check]
        let metadata_list_list =
          List.map
            (List.map (fun receipt ->
                 (* Check that the metadata are
                    serializable/deserializable *)
                 let bytes =
                   Data_encoding.Binary.to_bytes_exn
                     Proto
                     .operation_receipt_encoding_with_legacy_attestation_name
                     receipt
                 in
                 let _ =
                   Data_encoding.Binary.of_bytes_exn
                     Proto
                     .operation_receipt_encoding_with_legacy_attestation_name
                     bytes
                 in
                 let metadata =
                   match operation_metadata_size_limit with
                   | Shell_limits.Unlimited -> Metadata bytes
                   | Limited size_limit ->
                       if Bytes.length bytes < size_limit then Metadata bytes
                       else Too_large_metadata
                 in
                 (bytes, metadata)))
            ops_metadata
        in
        if [@time.duration_lwt metadata_hash] should_include_metadata_hashes
        then
          return
            (Metadata_hash
               (List.map
                  (List.map (fun (bytes, metadata) ->
                       let metadata_hash =
                         Operation_metadata_hash.hash_bytes [bytes]
                       in
                       (metadata, metadata_hash)))
                  metadata_list_list))
        else
          return (No_metadata_hash (List.map (List.map snd) metadata_list_list))
      with exn ->
        trace
          Validation_errors.Cannot_serialize_operation_metadata
          (tzfail (Exn exn))
    in
    return (block_metadata, ops_metadata)

  let prepare_context predecessor_block_metadata_hash
      predecessor_ops_metadata_hash (block_header : Proto.block_header)
      predecessor_context predecessor_hash =
    let open Lwt_result_syntax in
    let*! context =
      update_testchain_status
        predecessor_context
        ~predecessor_hash
        block_header.shell.timestamp
    in
    let*! context =
      match predecessor_block_metadata_hash with
      | None -> Lwt.return context
      | Some hash ->
          Context_ops.add_predecessor_block_metadata_hash context hash
    in
    let*! context =
      match predecessor_ops_metadata_hash with
      | None -> Lwt.return context
      | Some hash -> Context_ops.add_predecessor_ops_metadata_hash context hash
    in
    return context

  (* FIXME: This code is used by recompute_metadata but emitting time
     measurement events in proto_apply_operations should not impact
     current benchmarks.
     See https://gitlab.com/tezos/tezos/-/issues/2716 *)
  let proto_apply_operations chain_id context cache
      (predecessor_block_header : Block_header.t) block_header block_hash
      operations =
    let open Lwt_result_syntax in
    trace
      (invalid_block block_hash Economic_protocol_error)
      (let* state =
         (Proto.begin_application
            context
            chain_id
            (Application block_header)
            ~predecessor:predecessor_block_header.shell
            ~cache [@time.duration_lwt application_beginning])
       in
       let* state, ops_metadata =
         (List.fold_left_es
            (fun (state, acc) ops ->
              let* state, ops_metadata =
                List.fold_left_es
                  (fun (state, acc) (oph, op) ->
                    let* state, op_metadata =
                      Proto.apply_operation state oph op
                    in
                    return (state, op_metadata :: acc))
                  (state, [])
                  ops
              in
              return (state, List.rev ops_metadata :: acc))
            (state, [])
            operations [@time.duration_lwt operations_application])
       in
       let ops_metadata = List.rev ops_metadata in
       let* validation_result, block_data =
         (Proto.finalize_application
            state
            (Some block_header.shell) [@time.duration_lwt block_finalization])
       in
       return (validation_result, block_data, ops_metadata))

  let may_init_new_protocol chain_id new_protocol
      (block_header : Proto.block_header) block_hash
      (validation_result : Tezos_protocol_environment.validation_result) =
    let open Lwt_result_syntax in
    if Protocol_hash.equal new_protocol Proto.hash then
      return
        ( validation_result,
          Proto.environment_version,
          Proto.expected_context_hash )
    else
      match Registered_protocol.get new_protocol with
      | None ->
          tzfail
            (Unavailable_protocol {block = block_hash; protocol = new_protocol})
      | Some (module NewProto) ->
          let*? () =
            check_proto_environment_version_increasing
              block_hash
              Proto.environment_version
              NewProto.environment_version
          in
          let*! () =
            Validation_events.(emit new_protocol_initialisation new_protocol)
          in
          let* validation_result =
            NewProto.init chain_id validation_result.context block_header.shell
          in
          return
            ( validation_result,
              NewProto.environment_version,
              NewProto.expected_context_hash )

  let apply ?(simulate = false) ?cached_result chain_id ~cache
      ~user_activated_upgrades ~user_activated_protocol_overrides
      ~operation_metadata_size_limit ~max_operations_ttl
      ~(predecessor_block_header : Block_header.t)
      ~predecessor_block_metadata_hash ~predecessor_ops_metadata_hash
      ~predecessor_context ~predecessor_resulting_context_hash
      ~(block_header : Block_header.t) operations =
    let open Lwt_result_syntax in
    let block_hash = Block_header.hash block_header in
    let shell_header_hash = hash_shell_header block_header.shell in
    match cached_result with
    | Some (({result; _} as cached_result), context)
      when Shell_header_hash.equal result.shell_header_hash shell_header_hash ->
        (* In order to implement the preapply's cache mechanism, we
           need to differentiate blocks. Their hash cannot be used as
           a resulting preapply block does not contain correct
           protocol data (e.g. signature). Therefore, their hashes
           won't be the same. However, shell headers will remain the
           same thus we use those to discriminate blocks. *)
        let*! () = Validation_events.(emit using_preapply_result block_hash) in
        let*! context_hash =
          if simulate then
            Lwt.return
            @@ Context_ops.hash
                 ~time:block_header.shell.timestamp
                 ?message:result.validation_store.message
                 context
          else
            Context_ops.commit
              ~time:block_header.shell.timestamp
              ?message:result.validation_store.message
              context
        in
        assert (
          Context_hash.equal
            context_hash
            result.validation_store.resulting_context_hash) ;
        return cached_result
    | Some _ | None ->
        let* () =
          check_block_header
            ~predecessor_block_header
            ~predecessor_resulting_context_hash
            block_hash
            block_header
        in
        let* block_header = parse_block_header block_hash block_header in
        let* () = check_operation_quota block_hash operations in
        let predecessor_hash = Block_header.hash predecessor_block_header in
        let* operations =
          (parse_operations
             block_hash
             operations [@time.duration_lwt operations_parsing])
        in
        let* context =
          prepare_context
            predecessor_block_metadata_hash
            predecessor_ops_metadata_hash
            block_header
            predecessor_context
            predecessor_hash
        in
        let* validation_result, block_metadata, ops_metadata =
          proto_apply_operations
            chain_id
            context
            cache
            predecessor_block_header
            block_header
            block_hash
            operations
        in
        let*! validation_result =
          may_patch_protocol
            ~user_activated_upgrades
            ~user_activated_protocol_overrides
            ~level:block_header.shell.level
            validation_result
        in
        let context = validation_result.context in
        let*! new_protocol = Context_ops.get_protocol context in
        let expected_proto_level =
          if Protocol_hash.equal new_protocol Proto.hash then
            predecessor_block_header.shell.proto_level
          else (predecessor_block_header.shell.proto_level + 1) mod 256
        in
        let* () =
          fail_when
            (block_header.shell.proto_level <> expected_proto_level)
            (invalid_block
               block_hash
               (Invalid_proto_level
                  {
                    found = block_header.shell.proto_level;
                    expected = expected_proto_level;
                  }))
        in
        let* () =
          fail_when
            Fitness.(validation_result.fitness <> block_header.shell.fitness)
            (invalid_block
               block_hash
               (Invalid_fitness
                  {
                    expected = block_header.shell.fitness;
                    found = validation_result.fitness;
                  }))
        in
        let* validation_result, new_protocol_env_version, expected_context_hash
            =
          may_init_new_protocol
            chain_id
            new_protocol
            block_header
            block_hash
            validation_result
        in
        let max_operations_ttl =
          max
            0
            (min (max_operations_ttl + 1) validation_result.max_operations_ttl)
        in
        let validation_result = {validation_result with max_operations_ttl} in
        let* block_metadata, ops_metadata =
          compute_metadata
            ~operation_metadata_size_limit
            new_protocol_env_version
            block_metadata
            ops_metadata
        in
        let (Context {cache; _}) = validation_result.context in
        let context = validation_result.context in
        let*! resulting_context_hash =
          if simulate then
            Lwt.return
            @@ Context_ops.hash
                 ~time:block_header.shell.timestamp
                 ?message:validation_result.message
                 context
          else
            Context_ops.commit
              ~time:block_header.shell.timestamp
              ?message:validation_result.message
              context [@time.duration_lwt context_commitment] [@time.flush]
        in
        let* () =
          let is_context_consistent =
            match expected_context_hash with
            | Predecessor_resulting_context ->
                (* The check that the header's context is the
                   predecessor's resulting context has already been
                   performed in the [check_block_header] call above. *)
                true
            | Resulting_context ->
                Context_hash.equal
                  resulting_context_hash
                  block_header.shell.context
          in
          fail_unless
            is_context_consistent
            (Validation_errors.Inconsistent_hash
               (resulting_context_hash, block_header.shell.context))
        in
        let validation_store =
          {
            resulting_context_hash;
            timestamp = block_header.shell.timestamp;
            message = validation_result.message;
            max_operations_ttl = validation_result.max_operations_ttl;
            last_allowed_fork_level = validation_result.last_allowed_fork_level;
          }
        in
        return
          {
            result =
              {
                shell_header_hash = hash_shell_header block_header.shell;
                validation_store;
                block_metadata;
                ops_metadata;
              };
            cache;
          }

  let recompute_metadata chain_id ~cache
      ~(predecessor_block_header : Block_header.t)
      ~predecessor_block_metadata_hash ~predecessor_ops_metadata_hash
      ~predecessor_context ~(block_header : Block_header.t) operations =
    let open Lwt_result_syntax in
    let block_hash = Block_header.hash block_header in
    (* We assume that the block header and its associated operations
       have already been checked as valid. *)
    let* block_header = parse_block_header block_hash block_header in
    let predecessor_hash = Block_header.hash predecessor_block_header in
    let* context =
      prepare_context
        predecessor_block_metadata_hash
        predecessor_ops_metadata_hash
        block_header
        predecessor_context
        predecessor_hash
    in
    let* operations = parse_operations block_hash operations in
    let* validation_result, block_metadata, ops_metadata =
      proto_apply_operations
        chain_id
        context
        cache
        predecessor_block_header
        block_header
        block_hash
        operations
    in
    let context = validation_result.context in
    let*! new_protocol = Context_ops.get_protocol context in
    let* _validation_result, new_protocol_env_version, _expected_context_hash =
      may_init_new_protocol
        chain_id
        new_protocol
        block_header
        block_hash
        validation_result
    in
    compute_metadata
      ~operation_metadata_size_limit:Unlimited
      new_protocol_env_version
      block_metadata
      ops_metadata

  let preapply_operation pv op =
    let open Lwt_syntax in
    if Operation_hash.Set.mem op.hash pv.live_operations then return Outdated
    else
      let+ r =
        protect (fun () ->
            let operation : Proto.operation =
              {shell = op.raw.shell; protocol_data = op.protocol_data}
            in
            let open Lwt_result_syntax in
            let* validation_state =
              Proto.validate_operation pv.validation_state op.hash operation
            in
            let* application_state, receipt =
              Proto.apply_operation pv.application_state op.hash operation
            in
            return (validation_state, application_state, receipt))
      in
      match r with
      | Ok (validation_state, application_state, receipt) -> (
          let pv =
            {
              validation_state;
              application_state;
              applied = (op, receipt) :: pv.applied;
              live_blocks = pv.live_blocks;
              live_operations =
                Operation_hash.Set.add op.hash pv.live_operations;
            }
          in
          match
            Data_encoding.Binary.(
              of_bytes_exn
                Proto.operation_receipt_encoding_with_legacy_attestation_name
                (to_bytes_exn
                   Proto.operation_receipt_encoding_with_legacy_attestation_name
                   receipt))
          with
          | receipt -> Applied (pv, receipt)
          | exception exn ->
              Refused
                [Validation_errors.Cannot_serialize_operation_metadata; Exn exn]
          )
      | Error trace -> (
          match classify_trace trace with
          | Branch -> Branch_refused trace
          | Permanent -> Refused trace
          | Temporary -> Branch_delayed trace
          | Outdated -> Outdated)

  (** Doesn't depend on heavy [Registered_protocol.T] for testability. *)
  let safe_binary_of_bytes (encoding : 'a Data_encoding.t) (bytes : bytes) :
      'a tzresult =
    let open Result_syntax in
    match Data_encoding.Binary.of_bytes_opt encoding bytes with
    | None -> tzfail Parse_error
    | Some protocol_data -> return protocol_data

  let parse_unsafe (proto : bytes) : Proto.operation_data tzresult =
    safe_binary_of_bytes
      Proto.operation_data_encoding_with_legacy_attestation_name
      proto

  let parse (raw : Operation.t) =
    let open Result_syntax in
    let hash = Operation.hash raw in
    let size = Data_encoding.Binary.length Operation.encoding raw in
    if size > Proto.max_operation_data_length then
      tzfail (Oversized_operation {size; max = Proto.max_operation_data_length})
    else
      let* protocol_data = parse_unsafe raw.proto in
      return {hash; raw; protocol_data}

  let preapply ~chain_id ~cache ~user_activated_upgrades
      ~user_activated_protocol_overrides ~operation_metadata_size_limit
      ~protocol_data ~live_blocks ~live_operations ~timestamp
      ~predecessor_context ~predecessor_resulting_context_hash
      ~(predecessor_shell_header : Block_header.shell_header) ~predecessor_hash
      ~predecessor_max_operations_ttl ~predecessor_block_metadata_hash
      ~predecessor_ops_metadata_hash ~operations =
    let open Lwt_result_syntax in
    let context = predecessor_context in
    let*! context =
      update_testchain_status context ~predecessor_hash timestamp
    in
    let should_metadata_be_present =
      (* Block and operation metadata hashes may not be set on the
         testchain genesis block and activation block, even when they
         are using environment V1, they contain no operations. *)
      let is_from_genesis = predecessor_shell_header.validation_passes = 0 in
      Protocol.(
        match Proto.environment_version with
        | V0 -> false
        | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 -> true)
      && not is_from_genesis
    in
    let* context =
      match predecessor_block_metadata_hash with
      | None ->
          if should_metadata_be_present then
            tzfail (Missing_block_metadata_hash predecessor_hash)
          else return context
      | Some hash ->
          Lwt_result.ok
          @@ Context_ops.add_predecessor_block_metadata_hash context hash
    in
    let* context =
      match predecessor_ops_metadata_hash with
      | None ->
          if should_metadata_be_present then
            tzfail (Missing_operation_metadata_hashes predecessor_hash)
          else return context
      | Some hash ->
          Lwt_result.ok
          @@ Context_ops.add_predecessor_ops_metadata_hash context hash
    in
    let mode =
      Proto.Construction
        {predecessor_hash; timestamp; block_header_data = protocol_data}
    in
    let* validation_state =
      Proto.begin_validation
        context
        chain_id
        mode
        ~predecessor:predecessor_shell_header
        ~cache
    in
    let* application_state =
      Proto.begin_application
        context
        chain_id
        mode
        ~predecessor:predecessor_shell_header
        ~cache
    in
    let preapply_state =
      {
        validation_state;
        application_state;
        applied = [];
        live_blocks;
        live_operations;
      }
    in
    let apply_operation_with_preapply_result preapp t receipts op =
      let open Preapply_result in
      let*! r = preapply_operation t op in
      match r with
      | Applied (t, receipt) ->
          let applied = (op.hash, op.raw) :: preapp.applied in
          Lwt.return ({preapp with applied}, t, receipt :: receipts)
      | Branch_delayed errors ->
          let branch_delayed =
            Operation_hash.Map.add
              op.hash
              (op.raw, errors)
              preapp.branch_delayed
          in
          Lwt.return ({preapp with branch_delayed}, t, receipts)
      | Branch_refused errors ->
          let branch_refused =
            Operation_hash.Map.add
              op.hash
              (op.raw, errors)
              preapp.branch_refused
          in
          Lwt.return ({preapp with branch_refused}, t, receipts)
      | Refused errors ->
          let refused =
            Operation_hash.Map.add op.hash (op.raw, errors) preapp.refused
          in
          Lwt.return ({preapp with refused}, t, receipts)
      | Outdated -> Lwt.return (preapp, t, receipts)
    in
    let*! ( validation_passes,
            validation_result_list_rev,
            receipts_rev,
            validation_state ) =
      List.fold_left_s
        (fun ( acc_validation_passes,
               acc_validation_result_rev,
               receipts,
               acc_validation_state )
             operations ->
          let*! new_validation_result, new_validation_state, rev_receipts =
            List.fold_left_s
              (fun (acc_validation_result, acc_validation_state, receipts) op ->
                match parse op with
                | Error _ ->
                    (* FIXME: https://gitlab.com/tezos/tezos/-/issues/1721  *)
                    Lwt.return
                      (acc_validation_result, acc_validation_state, receipts)
                | Ok op ->
                    apply_operation_with_preapply_result
                      acc_validation_result
                      acc_validation_state
                      receipts
                      op)
              (Preapply_result.empty, acc_validation_state, [])
              operations
          in
          (* Applied operations are reverted ; revert to the initial ordering *)
          let new_validation_result =
            {
              new_validation_result with
              applied = List.rev new_validation_result.applied;
            }
          in
          Lwt.return
            ( acc_validation_passes + 1,
              new_validation_result :: acc_validation_result_rev,
              List.rev rev_receipts :: receipts,
              new_validation_state ))
        (0, [], [], preapply_state)
        operations
    in
    let validation_result_list = List.rev validation_result_list_rev in
    let applied_ops_metadata = List.rev receipts_rev in
    let preapply_state = validation_state in
    let operations_hash =
      Operation_list_list_hash.compute
        (List.rev_map
           (fun r ->
             Operation_list_hash.compute
               (List.map fst r.Preapply_result.applied))
           validation_result_list_rev)
    in
    let level = Int32.succ predecessor_shell_header.level in
    let shell_header : Block_header.shell_header =
      {
        level;
        proto_level = predecessor_shell_header.proto_level;
        predecessor = predecessor_hash;
        timestamp;
        validation_passes;
        operations_hash;
        context = Context_hash.zero (* place holder *);
        fitness = [];
      }
    in
    let* () = Proto.finalize_validation preapply_state.validation_state in
    let* validation_result, block_header_metadata =
      Proto.finalize_application
        preapply_state.application_state
        (Some shell_header)
    in
    let*! validation_result =
      may_patch_protocol
        ~user_activated_upgrades
        ~user_activated_protocol_overrides
        ~level
        validation_result
    in
    let*! protocol =
      Tezos_protocol_environment.Context.get_protocol validation_result.context
    in
    let proto_level =
      if Protocol_hash.equal protocol Proto.hash then
        predecessor_shell_header.proto_level
      else (predecessor_shell_header.proto_level + 1) mod 256
    in
    let shell_header : Block_header.shell_header =
      {shell_header with proto_level; fitness = validation_result.fitness}
    in
    let* ( validation_result,
           cache,
           new_protocol_env_version,
           proto_expected_context_hash ) =
      if Protocol_hash.equal protocol Proto.hash then
        let (Tezos_protocol_environment.Context.Context {cache; _}) =
          validation_result.context
        in
        return
          ( validation_result,
            cache,
            Proto.environment_version,
            Proto.expected_context_hash )
      else
        match Registered_protocol.get protocol with
        | None ->
            tzfail
              (Block_validator_errors.Unavailable_protocol
                 {block = predecessor_hash; protocol})
        | Some (module NewProto) ->
            let*? () =
              check_proto_environment_version_increasing
                Block_hash.zero
                Proto.environment_version
                NewProto.environment_version
            in
            let* validation_result =
              NewProto.init chain_id validation_result.context shell_header
            in
            let (Tezos_protocol_environment.Context.Context {cache; _}) =
              validation_result.context
            in
            let*! () =
              Validation_events.(emit new_protocol_initialisation NewProto.hash)
            in
            return
              ( validation_result,
                cache,
                NewProto.environment_version,
                NewProto.expected_context_hash )
    in
    let context = validation_result.context in
    let resulting_context_hash =
      Context_ops.hash
        ?message:validation_result.message
        ~time:timestamp
        context
    in
    let header_context_hash =
      match proto_expected_context_hash with
      | Resulting_context -> resulting_context_hash
      | Predecessor_resulting_context -> predecessor_resulting_context_hash
    in
    let preapply_result =
      ({shell_header with context = header_context_hash}, validation_result_list)
    in
    let* block_metadata, ops_metadata =
      compute_metadata
        ~operation_metadata_size_limit
        new_protocol_env_version
        block_header_metadata
        applied_ops_metadata
    in
    let max_operations_ttl =
      max
        0
        (min
           (predecessor_max_operations_ttl + 1)
           validation_result.max_operations_ttl)
    in
    let result =
      let validation_store =
        {
          resulting_context_hash;
          timestamp;
          message = validation_result.message;
          max_operations_ttl;
          last_allowed_fork_level = validation_result.last_allowed_fork_level;
        }
      in
      let result =
        {
          shell_header_hash = hash_shell_header (fst preapply_result);
          validation_store;
          block_metadata;
          ops_metadata;
        }
      in
      {result; cache}
    in
    return (preapply_result, (result, context))

  let precheck block_hash chain_id ~(predecessor_block_header : Block_header.t)
      ~predecessor_block_hash ~predecessor_context
      ~predecessor_resulting_context_hash ~cache
      ~(block_header : Block_header.t) operations =
    let open Lwt_result_syntax in
    let* () =
      check_block_header
        ~predecessor_block_header
        ~predecessor_resulting_context_hash
        block_hash
        block_header
    in
    let* block_header = parse_block_header block_hash block_header in
    let* () = check_operation_quota block_hash operations in
    let*! context =
      update_testchain_status
        predecessor_context
        ~predecessor_hash:predecessor_block_hash
        block_header.shell.timestamp
    in
    let* operations = parse_operations block_hash operations in
    let* state =
      Proto.begin_validation
        context
        chain_id
        (Application block_header)
        ~predecessor:predecessor_block_header.shell
        ~cache
    in
    let* state =
      List.fold_left_es
        (fun state ops ->
          List.fold_left_es
            (fun state (oph, op) ->
              let* state = Proto.validate_operation state oph op in
              return state)
            state
            ops)
        state
        operations
    in
    let* () = Proto.finalize_validation state in
    return_unit

  let precheck chain_id ~(predecessor_block_header : Block_header.t)
      ~predecessor_block_hash ~predecessor_context
      ~predecessor_resulting_context_hash ~cache
      ~(block_header : Block_header.t) operations =
    let block_hash = Block_header.hash block_header in
    trace (invalid_block block_hash Economic_protocol_error)
    @@ precheck
         block_hash
         chain_id
         ~predecessor_block_header
         ~predecessor_block_hash
         ~predecessor_context
         ~predecessor_resulting_context_hash
         ~cache
         ~block_header
         operations
end

let assert_no_duplicate_operations block_hash live_operations operations =
  let open Result_syntax in
  let exception Duplicate of block_error in
  try
    return
      (List.fold_left
         (List.fold_left (fun live_operations op ->
              let oph = Operation.hash op in
              if Operation_hash.Set.mem oph live_operations then
                raise (Duplicate (Replayed_operation oph))
              else Operation_hash.Set.add oph live_operations))
         live_operations
         operations)
  with Duplicate err -> tzfail (invalid_block block_hash err)

let assert_operation_liveness block_hash live_blocks operations =
  let open Result_syntax in
  let exception Outdated of block_error in
  try
    return
      (List.iter
         (List.iter (fun op ->
              if not (Block_hash.Set.mem op.Operation.shell.branch live_blocks)
              then
                let error =
                  Outdated_operation
                    {
                      operation = Operation.hash op;
                      originating_block = op.shell.branch;
                    }
                in
                raise (Outdated error)))
         operations)
  with Outdated err -> tzfail (invalid_block block_hash err)

(* Maybe this function should be moved somewhere else since it used
   once by [Block_validator_process] *)
let check_liveness ~live_blocks ~live_operations block_hash operations =
  let open Result_syntax in
  let* (_ : Operation_hash.Set.t) =
    assert_no_duplicate_operations block_hash live_operations operations
  in
  assert_operation_liveness block_hash live_blocks operations

type apply_environment = {
  max_operations_ttl : int;
  chain_id : Chain_id.t;
  predecessor_block_header : Block_header.t;
  predecessor_context : Tezos_protocol_environment.Context.t;
  predecessor_resulting_context_hash : Context_hash.t;
  predecessor_block_metadata_hash : Block_metadata_hash.t option;
  predecessor_ops_metadata_hash : Operation_metadata_list_list_hash.t option;
  user_activated_upgrades : User_activated.upgrades;
  user_activated_protocol_overrides : User_activated.protocol_overrides;
  operation_metadata_size_limit : Shell_limits.operation_metadata_size_limit;
}

let recompute_metadata chain_id ~predecessor_block_header ~predecessor_context
    ~predecessor_block_metadata_hash ~predecessor_ops_metadata_hash ~cache
    block_hash block_header operations =
  let open Lwt_result_syntax in
  let*! pred_protocol_hash = Context_ops.get_protocol predecessor_context in
  let* (module Proto) =
    match Registered_protocol.get pred_protocol_hash with
    | None ->
        tzfail
          (Unavailable_protocol
             {block = block_hash; protocol = pred_protocol_hash})
    | Some p -> return p
  in
  let module Block_validation = Make (Proto) in
  Block_validation.recompute_metadata
    chain_id
    ~predecessor_block_header
    ~predecessor_block_metadata_hash
    ~predecessor_ops_metadata_hash
    ~predecessor_context
    ~cache
    ~block_header
    operations

let recompute_metadata ~chain_id ~predecessor_block_header ~predecessor_context
    ~predecessor_block_metadata_hash ~predecessor_ops_metadata_hash
    ~block_header ~operations ~cache =
  let open Lwt_result_syntax in
  let block_hash = Block_header.hash block_header in
  let*! r =
    recompute_metadata
      chain_id
      ~predecessor_block_header
      ~predecessor_context
      ~predecessor_block_metadata_hash
      ~predecessor_ops_metadata_hash
      ~cache
      block_hash
      block_header
      operations
  in
  match r with
  | Error (Exn (Unix.Unix_error (errno, fn, msg)) :: _) ->
      tzfail (System_error {errno = Unix.error_message errno; fn; msg})
  | (Ok _ | Error _) as res -> Lwt.return res

let precheck ~chain_id ~predecessor_block_header ~predecessor_block_hash
    ~predecessor_context ~predecessor_resulting_context_hash ~cache block_header
    operations =
  let open Lwt_result_syntax in
  let block_hash = Block_header.hash block_header in
  let*! pred_protocol_hash = Context_ops.get_protocol predecessor_context in
  let* (module Proto) =
    match Registered_protocol.get pred_protocol_hash with
    | None ->
        tzfail
          (Unavailable_protocol
             {block = block_hash; protocol = pred_protocol_hash})
    | Some p -> return p
  in
  let module Block_validation = Make (Proto) in
  Block_validation.precheck
    chain_id
    ~predecessor_block_header
    ~predecessor_block_hash
    ~predecessor_context
    ~predecessor_resulting_context_hash
    ~cache
    ~block_header
    operations

let apply ?simulate ?cached_result ?(should_precheck = true)
    {
      chain_id;
      user_activated_upgrades;
      user_activated_protocol_overrides;
      operation_metadata_size_limit;
      max_operations_ttl;
      predecessor_block_header;
      predecessor_block_metadata_hash;
      predecessor_ops_metadata_hash;
      predecessor_context;
      predecessor_resulting_context_hash;
    } ~cache block_hash block_header operations =
  let open Lwt_result_syntax in
  let*! pred_protocol_hash = Context_ops.get_protocol predecessor_context in
  let* (module Proto) =
    match Registered_protocol.get pred_protocol_hash with
    | None ->
        tzfail
          (Unavailable_protocol
             {block = block_hash; protocol = pred_protocol_hash})
    | Some p -> return p
  in
  let* () =
    if should_precheck && Proto.(compare environment_version V7 >= 0) then
      precheck
        ~chain_id
        ~predecessor_block_header
        ~predecessor_block_hash:block_header.Block_header.shell.predecessor
        ~predecessor_context
        ~predecessor_resulting_context_hash
        ~cache
        block_header
        operations
    else return_unit
  in
  let module Block_validation = Make (Proto) in
  Block_validation.apply
    ?simulate
    ?cached_result
    chain_id
    ~user_activated_upgrades
    ~user_activated_protocol_overrides
    ~operation_metadata_size_limit
    ~max_operations_ttl
    ~predecessor_block_header
    ~predecessor_block_metadata_hash
    ~predecessor_ops_metadata_hash
    ~predecessor_context
    ~predecessor_resulting_context_hash
    ~cache
    ~block_header
    operations

let apply ?simulate ?cached_result ?should_precheck apply_environment ~cache
    block_header operations =
  let open Lwt_result_syntax in
  let block_hash = Block_header.hash block_header in
  let*! r =
    (* The cache might be inconsistent with the context. By forcing
       the reloading of the cache, we restore the consistency. *)
    let*! r =
      apply
        ?simulate
        ?cached_result
        ?should_precheck
        apply_environment
        ~cache
        block_hash
        block_header
        operations
    in
    match r with
    | Error (Validation_errors.Inconsistent_hash _ :: _) ->
        (* The shell makes an assumption over the protocol concerning
           the cache which may be broken. In that case, the application
           fails with an [Inconsistency_hash] error. To make the node
           resilient to such problem, when such an error occurs, we retry
           the application using a fresh cache. *)
        let*! () = Event.(emit inherited_inconsistent_cache) block_hash in
        apply
          ?cached_result
          ?should_precheck
          apply_environment
          ~cache:`Force_load
          block_hash
          block_header
          operations
    | (Ok _ | Error _) as res -> Lwt.return res
  in
  match r with
  | Error (Exn (Unix.Unix_error (errno, fn, msg)) :: _) ->
      tzfail (System_error {errno = Unix.error_message errno; fn; msg})
  | (Ok _ | Error _) as res -> Lwt.return res

let preapply ~chain_id ~user_activated_upgrades
    ~user_activated_protocol_overrides ~operation_metadata_size_limit ~timestamp
    ~protocol_data ~live_blocks ~live_operations ~predecessor_context
    ~predecessor_resulting_context_hash ~predecessor_shell_header
    ~predecessor_hash ~predecessor_max_operations_ttl
    ~predecessor_block_metadata_hash ~predecessor_ops_metadata_hash operations =
  let open Lwt_result_syntax in
  let*! protocol = Context_ops.get_protocol predecessor_context in
  let* (module Proto) =
    match Registered_protocol.get protocol with
    | None ->
        (* FIXME: https://gitlab.com/tezos/tezos/-/issues/1718 *)
        (* This should not happen: it should be handled in the validator. *)
        failwith
          "Prevalidation: missing protocol '%a' for the current block."
          Protocol_hash.pp_short
          protocol
    | Some protocol -> return protocol
  in
  (* The cache might be inconsistent with the context. By forcing the
     reloading of the cache, we restore the consistency. *)
  let module Block_validation = Make (Proto) in
  let* protocol_data =
    match
      Data_encoding.Binary.of_bytes_opt
        Proto.block_header_data_encoding
        protocol_data
    with
    | None -> failwith "Invalid block header"
    | Some protocol_data -> return protocol_data
  in
  Block_validation.preapply
    ~chain_id
    ~cache:`Force_load
    ~user_activated_upgrades
    ~user_activated_protocol_overrides
    ~operation_metadata_size_limit
    ~protocol_data
    ~live_blocks
    ~live_operations
    ~timestamp
    ~predecessor_context
    ~predecessor_resulting_context_hash
    ~predecessor_shell_header
    ~predecessor_hash
    ~predecessor_max_operations_ttl
    ~predecessor_block_metadata_hash
    ~predecessor_ops_metadata_hash
    ~operations

let preapply ~chain_id ~user_activated_upgrades
    ~user_activated_protocol_overrides ~operation_metadata_size_limit ~timestamp
    ~protocol_data ~live_blocks ~live_operations ~predecessor_context
    ~predecessor_resulting_context_hash ~predecessor_shell_header
    ~predecessor_hash ~predecessor_max_operations_ttl
    ~predecessor_block_metadata_hash ~predecessor_ops_metadata_hash operations =
  let open Lwt_result_syntax in
  let*! r =
    preapply
      ~chain_id
      ~user_activated_upgrades
      ~user_activated_protocol_overrides
      ~operation_metadata_size_limit
      ~timestamp
      ~protocol_data
      ~live_blocks
      ~live_operations
      ~predecessor_context
      ~predecessor_resulting_context_hash
      ~predecessor_shell_header
      ~predecessor_hash
      ~predecessor_max_operations_ttl
      ~predecessor_block_metadata_hash
      ~predecessor_ops_metadata_hash
      operations
  in
  match r with
  | Error (Exn (Unix.Unix_error (errno, fn, msg)) :: _) ->
      tzfail (System_error {errno = Unix.error_message errno; fn; msg})
  | (Ok _ | Error _) as res -> Lwt.return res
back to top