https://gitlab.com/tezos/tezos
Raw File
Tip revision: 2d92d0c8fccbd6dbc88ff03962c8c7982a63d119 authored by Arvid Jakobsson on 08 December 2021, 11:33:10 UTC
Tests: fix Invocation headers
Tip revision: 2d92d0c
block_directory.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com>     *)
(* Copyright (c) 2019-2021 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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

let read_partial_context =
  let init = Block_services.Dir TzString.Map.empty in
  fun context path depth ->
    if depth = 0 then Lwt.return Block_services.Cut
    else
      (* According to the documentation of Context.fold,
         "[f] is never called with an empty key for values; i.e.,
           folding over a value is a no-op".
         Therefore, we first need to check that whether its a value.
      *)
      Context.find context path >>= function
      | Some v -> Lwt.return (Block_services.Key v)
      | None ->
          (* try to read as directory *)
          Context.fold
            ~depth:(`Le depth)
            context
            path
            ~order:`Sorted
            ~init
            ~f:(fun k tree acc ->
              let open Block_services in
              if List.compare_length_with k depth >= 0 then
                (* only [=] case is possible because [~depth] is [(`Le depth)] *)
                Lwt.return (raw_context_insert (k, Cut) acc)
              else
                Context.Tree.to_value tree >|= function
                | None -> acc
                | Some v -> raw_context_insert (k, Key v) acc)

let build_raw_header_rpc_directory (module Proto : Block_services.PROTO) =
  let dir :
      (Store.chain_store * Block_hash.t * Block_header.t) RPC_directory.t ref =
    ref RPC_directory.empty
  in
  let register0 s f =
    dir :=
      RPC_directory.register !dir (RPC_service.subst0 s) (fun block p q ->
          f block p q)
  in
  let module Block_services = Block_services.Make (Proto) (Proto) in
  let module S = Block_services.S in
  register0 S.hash (fun (_, hash, _) () () -> return hash) ;
  (* block header *)
  register0 S.header (fun (chain_store, hash, header) () () ->
      let protocol_data =
        Data_encoding.Binary.of_bytes_exn
          Proto.block_header_data_encoding
          header.protocol_data
      in
      return
        {
          Block_services.hash;
          chain_id = Store.Chain.chain_id chain_store;
          shell = header.shell;
          protocol_data;
        }) ;
  register0 S.raw_header (fun (_, _, header) () () ->
      return (Data_encoding.Binary.to_bytes_exn Block_header.encoding header)) ;
  register0 S.Header.shell_header (fun (_, _, header) () () ->
      return header.shell) ;
  register0 S.Header.protocol_data (fun (_, _, header) () () ->
      return
        (Data_encoding.Binary.of_bytes_exn
           Proto.block_header_data_encoding
           header.protocol_data)) ;
  register0 S.Header.raw_protocol_data (fun (_, _, header) () () ->
      return header.protocol_data) ;
  (* helpers *)
  register0 S.Helpers.Forge.block_header (fun _block () header ->
      return (Data_encoding.Binary.to_bytes_exn Block_header.encoding header)) ;
  (* protocols *)
  register0 S.protocols (fun (chain_store, _hash, header) () () ->
      Store.Chain.find_protocol
        chain_store
        ~protocol_level:header.shell.proto_level
      >>= fun next_proto ->
      let next_protocol_hash =
        WithExceptions.Option.to_exn ~none:Not_found next_proto
      in
      Store.Block.read_block_opt chain_store header.shell.predecessor
      >>= function
      | None ->
          return
            {
              Tezos_shell_services.Block_services.current_protocol =
                next_protocol_hash;
              next_protocol = next_protocol_hash;
            }
      | Some pred_block ->
          let pred_header = Store.Block.header pred_block in
          Store.Chain.find_protocol
            chain_store
            ~protocol_level:pred_header.shell.proto_level
          >>= fun current_protocol ->
          let protocol_hash =
            WithExceptions.Option.to_exn ~none:Not_found current_protocol
          in
          return
            {
              Tezos_shell_services.Block_services.current_protocol =
                protocol_hash;
              next_protocol = next_protocol_hash;
            }) ;
  !dir

let build_raw_rpc_directory (module Proto : Block_services.PROTO)
    (module Next_proto : Registered_protocol.T) =
  let dir : (Store.chain_store * Store.Block.block) RPC_directory.t ref =
    ref RPC_directory.empty
  in
  let merge d = dir := RPC_directory.merge d !dir in
  let register0 s f =
    dir :=
      RPC_directory.register !dir (RPC_service.subst0 s) (fun block p q ->
          f block p q)
  in
  let register1 s f =
    dir :=
      RPC_directory.register !dir (RPC_service.subst1 s) (fun (block, a) p q ->
          f block a p q)
  in
  let register2 s f =
    dir :=
      RPC_directory.register
        !dir
        (RPC_service.subst2 s)
        (fun ((block, a), b) p q -> f block a b p q)
  in
  let module Block_services = Block_services.Make (Proto) (Next_proto) in
  let module S = Block_services.S in
  register0 S.live_blocks (fun (chain_store, block) () () ->
      Store.Chain.compute_live_blocks chain_store ~block
      >>=? fun (live_blocks, _) -> return live_blocks) ;
  (* block metadata *)
  let block_metadata chain_store block =
    Store.Block.get_block_metadata chain_store block >>=? fun metadata ->
    let protocol_data =
      Data_encoding.Binary.of_bytes_exn
        Proto.block_header_metadata_encoding
        (Store.Block.block_metadata metadata)
    in
    Store.Block.testchain_status chain_store block
    >>=? fun (test_chain_status, _) ->
    let max_operations_ttl = Store.Block.max_operations_ttl metadata in
    return
      {
        Block_services.protocol_data;
        test_chain_status;
        max_operations_ttl;
        max_operation_data_length = Next_proto.max_operation_data_length;
        max_block_header_length = Next_proto.max_block_length;
        operation_list_quota =
          List.map
            (fun {Tezos_protocol_environment.max_size; max_op} ->
              {Tezos_shell_services.Block_services.max_size; max_op})
            Next_proto.validation_passes;
      }
  in
  register0 S.metadata (fun (chain_store, block) () () ->
      block_metadata chain_store block) ;
  let fail_opt = function None -> Lwt.fail Not_found | Some v -> return v in
  register0 S.metadata_hash (fun (_, block) () () ->
      fail_opt (Store.Block.block_metadata_hash block)) ;
  (* operations *)
  let convert_with_metadata chain_id (op : Operation.t) metadata :
      Block_services.operation =
    let protocol_data =
      Data_encoding.Binary.of_bytes_exn Proto.operation_data_encoding op.proto
    in
    let receipt =
      Some
        (Data_encoding.Binary.of_bytes_exn
           Proto.operation_receipt_encoding
           metadata)
    in
    {
      Block_services.chain_id;
      hash = Operation.hash op;
      shell = op.shell;
      protocol_data;
      receipt;
    }
  in
  let convert_without_metadata chain_id (op : Operation.t) =
    let protocol_data =
      Data_encoding.Binary.of_bytes_exn Proto.operation_data_encoding op.proto
    in
    {
      Block_services.chain_id;
      hash = Operation.hash op;
      shell = op.shell;
      protocol_data;
      receipt = None;
    }
  in
  let operations chain_store block =
    let chain_id = Store.Chain.chain_id chain_store in
    let ops = Store.Block.operations block in
    Store.Block.get_block_metadata_opt chain_store block >>= function
    | None ->
        return (List.map (List.map (convert_without_metadata chain_id)) ops)
    | Some metadata ->
        Lwt.catch
          (fun () ->
            let ops_metadata = Store.Block.operations_metadata metadata in
            List.map2_e
              ~when_different_lengths:()
              (List.map2
                 ~when_different_lengths:()
                 (convert_with_metadata chain_id))
              ops
              ops_metadata
            |> function
            | Ok v -> return v
            | Error () -> raise Not_found)
          (fun _ ->
            return (List.map (List.map (convert_without_metadata chain_id)) ops))
  in
  (*****************************************************************)
  register0 S.Operations.operations (fun (chain_store, block) () () ->
      operations chain_store block) ;
  register1 S.Operations.operations_in_pass (fun (chain_store, block) i () () ->
      let chain_id = Store.Chain.chain_id chain_store in
      let (ops, _path) = Store.Block.operations_path block i in
      Lwt.catch
        (fun () ->
          Store.Block.get_block_metadata_opt chain_store block >>= function
          | None -> return (List.map (convert_without_metadata chain_id) ops)
          | Some metadata -> (
              let opss_metadata = Store.Block.operations_metadata metadata in
              let ops_metadata =
                List.nth opss_metadata i
                |> WithExceptions.Option.to_exn ~none:Not_found
              in
              List.map2
                ~when_different_lengths:()
                (convert_with_metadata chain_id)
                ops
                ops_metadata
              |> function
              | Ok x -> return x
              | _ -> raise Not_found))
        (fun _ -> Lwt.fail Not_found)) ;
  register2 S.Operations.operation (fun (chain_store, block) i j () () ->
      let chain_id = Store.Chain.chain_id chain_store in
      Lwt.catch
        (fun () ->
          let (ops, _path) = Store.Block.operations_path block i in
          let op =
            List.nth ops j |> WithExceptions.Option.to_exn ~none:Not_found
          in
          Store.Block.get_block_metadata_opt chain_store block >>= function
          | None -> return (convert_without_metadata chain_id op)
          | Some metadata ->
              let opss_metadata = Store.Block.operations_metadata metadata in
              let ops_metadata =
                List.nth opss_metadata i
                |> WithExceptions.Option.to_exn ~none:Not_found
              in
              let op_metadata =
                List.nth ops_metadata j
                |> WithExceptions.Option.to_exn ~none:Not_found
              in
              return (convert_with_metadata chain_id op op_metadata))
        (fun _ -> Lwt.fail Not_found)) ;
  (* operation_hashes *)
  register0 S.Operation_hashes.operation_hashes (fun (_, block) () () ->
      return (Store.Block.all_operation_hashes block)) ;
  register1
    S.Operation_hashes.operation_hashes_in_pass
    (fun (_, block) i () () ->
      return (Store.Block.operations_hashes_path block i |> fst)) ;
  register2 S.Operation_hashes.operation_hash (fun (_, block) i j () () ->
      Lwt.catch
        (fun () ->
          let (ops, _) = Store.Block.operations_hashes_path block i in
          return (List.nth ops j |> WithExceptions.Option.to_exn ~none:Not_found))
        (fun _ -> Lwt.fail Not_found)) ;
  (* operation_metadata_hashes *)
  register0 S.Operation_metadata_hashes.root (fun (_, block) () () ->
      fail_opt (Store.Block.all_operations_metadata_hash block)) ;
  register0
    S.Operation_metadata_hashes.operation_metadata_hashes
    (fun (_, block) () () ->
      fail_opt (Store.Block.operations_metadata_hashes block)) ;
  register1
    S.Operation_metadata_hashes.operation_metadata_hashes_in_pass
    (fun (_, block) i () () ->
      fail_opt (Store.Block.operations_metadata_hashes block)
      >>=? fun ops_metadata_hashes ->
      fail_opt (List.nth_opt ops_metadata_hashes i)) ;
  register2
    S.Operation_metadata_hashes.operation_metadata_hash
    (fun (_, block) i j () () ->
      Lwt.catch
        (fun () ->
          fail_opt (Store.Block.operations_metadata_hashes_path block i)
          >>=? fun hashes ->
          return
            (List.nth hashes j |> WithExceptions.Option.to_exn ~none:Not_found))
        (fun _ -> Lwt.fail Not_found)) ;
  (* context *)
  register1 S.Context.read (fun (chain_store, block) path q () ->
      let depth = Option.value ~default:max_int q#depth in
      (* [depth] is defined as a [uint] not an [int] *)
      assert (depth >= 0) ;
      Store.Block.context chain_store block >>=? fun context ->
      Context.mem context path >>= fun mem ->
      Context.mem_tree context path >>= fun dir_mem ->
      if not (mem || dir_mem) then Lwt.fail Not_found
      else read_partial_context context path depth >>= Lwt.return_ok) ;
  register1 S.Context.merkle_tree (fun (chain_store, block) path query () ->
      Store.Block.context_opt chain_store block >>= function
      | None -> return None
      | Some context ->
          let holey = Option.value ~default:false query#holey in
          let leaf_kind =
            let open Tezos_shell_services.Block_services in
            if holey then Hole else Raw_context
          in
          Context.merkle_tree context leaf_kind path >>= return_some) ;
  (* info *)
  register0 S.info (fun (chain_store, block) () () ->
      let chain_id = Store.Chain.chain_id chain_store in
      let hash = Store.Block.hash block in
      let header = Store.Block.header block in
      let shell = header.shell in
      let protocol_data =
        Data_encoding.Binary.of_bytes_exn
          Proto.block_header_data_encoding
          header.protocol_data
      in
      (block_metadata chain_store block >>= function
       | Ok metadata -> return_some metadata
       | Error _ -> return_none)
      >>=? fun metadata ->
      operations chain_store block >>=? fun operations ->
      return
        {
          Block_services.hash;
          chain_id;
          header = {shell; protocol_data};
          metadata;
          operations;
        }) ;
  (* helpers *)
  register0 S.Helpers.Preapply.block (fun (chain_store, block) q p ->
      let timestamp =
        match q#timestamp with
        | None -> Time.System.to_protocol (Systime_os.now ())
        | Some time -> time
      in
      let protocol_data =
        Data_encoding.Binary.to_bytes_exn
          Next_proto.block_header_data_encoding
          p.protocol_data
      in
      let operations =
        List.map
          (fun operations ->
            let operations =
              if q#sort_operations then
                List.sort Next_proto.relative_position_within_block operations
              else operations
            in
            List.map
              (fun op ->
                let proto =
                  Data_encoding.Binary.to_bytes_exn
                    Next_proto.operation_data_encoding
                    op.Next_proto.protocol_data
                in
                {Operation.shell = op.shell; proto})
              operations)
          p.operations
      in
      (try return (Block_validator.running_worker ())
       with _ -> failwith "Block validator is not running")
      >>=? fun bv ->
      Block_validator.preapply
        bv
        chain_store
        ~predecessor:block
        ~timestamp
        ~protocol_data
        operations) ;
  register0 S.Helpers.Preapply.operations (fun (chain_store, block) () ops ->
      Store.Block.context chain_store block >>=? fun ctxt ->
      let predecessor = Store.Block.hash block in
      let header = Store.Block.shell_header block in
      let predecessor_context = Shell_context.wrap_disk_context ctxt in
      Next_proto.begin_construction
        ~chain_id:(Store.Chain.chain_id chain_store)
        ~predecessor_context
        ~predecessor_timestamp:header.timestamp
        ~predecessor_level:header.level
        ~predecessor_fitness:header.fitness
        ~predecessor
        ~timestamp:(Time.System.to_protocol (Systime_os.now ()))
        ~cache:`Lazy
        ()
      >>=? fun state ->
      List.fold_left_es
        (fun (state, acc) op ->
          Next_proto.apply_operation state op >>=? fun (state, result) ->
          return (state, (op.protocol_data, result) :: acc))
        (state, [])
        ops
      >>=? fun (state, acc) ->
      (* A pre application must not commit into the protocol caches.
         Hence, we set [cache_nonce] to None. *)
      Next_proto.finalize_block state None >>=? fun _ -> return (List.rev acc)) ;
  register1 S.Helpers.complete (fun (chain_store, block) prefix () () ->
      Store.Block.context chain_store block >>=? fun ctxt ->
      Base58.complete prefix >>= fun l1 ->
      let ctxt = Shell_context.wrap_disk_context ctxt in
      Next_proto.complete_b58prefix ctxt prefix >>= fun l2 -> return (l1 @ l2)) ;
  (* merge protocol rpcs... *)
  merge
    (RPC_directory.map
       (fun (chain_store, block) ->
         let hash = Store.Block.hash block in
         let header = Store.Block.header block in
         Lwt.return (chain_store, hash, header))
       (build_raw_header_rpc_directory (module Proto))) ;
  let proto_services =
    match Prevalidator_filters.find Next_proto.hash with
    | Some (module Filters) -> Filters.RPC.rpc_services
    | None -> Next_proto.rpc_services
  in
  merge
    (RPC_directory.map
       (fun (chain_store, block) ->
         ( Store.Block.context_exn chain_store block >>= fun context ->
           let predecessor_context = Shell_context.wrap_disk_context context in
           let chain_id = Store.Chain.chain_id chain_store in
           let Block_header.
                 {
                   timestamp = predecessor_timestamp;
                   level = predecessor_level;
                   fitness = predecessor_fitness;
                   _;
                 } =
             Store.Block.shell_header block
           in
           (*
             Reactivity is important when executing RPCs and there are
             no constraints to be consistent with other nodes. For this
             reason, the RPC directory loads the cache lazily.
             See {!Environment_context.source_of_cache}.
           *)
           let predecessor = Store.Block.hash block in
           let timestamp = Time.System.to_protocol (Systime_os.now ()) in
           Next_proto.value_of_key
             ~chain_id
             ~predecessor_context
             ~predecessor_timestamp
             ~predecessor_level
             ~predecessor_fitness
             ~predecessor
             ~timestamp
           >>=? fun value_of_key ->
           Tezos_protocol_environment.Context.load_cache
             predecessor
             predecessor_context
             `Lazy
             value_of_key
           >>=? fun context ->
           return
             {
               Tezos_protocol_environment.block_hash = Store.Block.hash block;
               block_header = Store.Block.shell_header block;
               context;
             } )
         >>= function
         | Ok result -> Lwt.return result
         | Error _ -> Lwt.fail Not_found)
       proto_services) ;
  !dir

let get_protocol hash =
  match Registered_protocol.get hash with
  | None -> raise Not_found
  | Some protocol -> protocol

let get_directory chain_store block =
  Store.Chain.get_rpc_directory chain_store block >>= function
  | Some dir -> Lwt.return dir
  | None -> (
      Store.Block.protocol_hash_exn chain_store block
      >>= fun next_protocol_hash ->
      let (module Next_proto) = get_protocol next_protocol_hash in
      let build_fake_rpc_directory () =
        build_raw_rpc_directory
          (module Block_services.Fake_protocol)
          (module Next_proto)
      in
      if Store.Block.is_genesis chain_store (Store.Block.hash block) then
        Lwt.return (build_fake_rpc_directory ())
      else
        (Store.Block.read_predecessor_opt chain_store block >>= function
         | None ->
             (* No predecessors (e.g. pruned caboose), return the
                current protocol *)
             Lwt.return (module Next_proto : Registered_protocol.T)
         | Some pred ->
             Store.Chain.savepoint chain_store >>= fun (_, savepoint_level) ->
             (if Compare.Int32.(Store.Block.level pred < savepoint_level) then
              Store.Chain.find_protocol
                chain_store
                ~protocol_level:(Store.Block.proto_level pred)
              >>= fun predecessor_protocol ->
              let protocol_hash =
                WithExceptions.Option.to_exn
                  ~none:Not_found
                  predecessor_protocol
              in
              Lwt.return protocol_hash
             else Store.Block.protocol_hash_exn chain_store pred)
             >>= fun protocol_hash -> Lwt.return (get_protocol protocol_hash))
        >>= fun (module Proto) ->
        Store.Chain.get_rpc_directory chain_store block >>= function
        | Some dir -> Lwt.return dir
        | None ->
            let dir =
              build_raw_rpc_directory (module Proto) (module Next_proto)
            in
            Store.Chain.set_rpc_directory
              chain_store
              ~protocol_hash:Proto.hash
              ~next_protocol_hash:Next_proto.hash
              dir
            >>= fun () -> Lwt.return dir)

let build_rpc_directory chain_store block =
  Store.Chain.block_of_identifier_opt chain_store block >>= function
  | None -> Lwt.fail Not_found
  | Some b ->
      get_directory chain_store b >>= fun dir ->
      Lwt.return (RPC_directory.map (fun _ -> Lwt.return (chain_store, b)) dir)
back to top