Revision 8441e6ca77e4e8cbac0d0d5f7a7af2ea38d28a4d authored by Andrea Cerone on 03 May 2023, 12:06:34 UTC, committed by Marge Bot on 15 May 2023, 19:21:01 UTC
1 parent ddc857b
light_core.ml
(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 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. *)
(* *)
(*****************************************************************************)
(** Module providing the light's mode implementation of Proxy.CORE *)
module Logger = Light_logger.Logger
module Store = Tezos_context_memory.Context
module Consensus = Light_consensus
module Block_services = Tezos_shell_services.Block_services
module Proof = Tezos_context_sigs.Context.Proof_types
let key_to_string = String.concat ";"
let chain_n_block_to_string chain block =
Format.asprintf
"chain: %s, block: %s"
(Block_services.chain_to_string chain)
(Block_services.to_string block)
let light_failwith (pgi : Proxy.proxy_getter_input) ?(warn_symbolic = false) msg
=
let open Lwt_syntax in
let symbolic_block = Light.hash_of_block pgi.block |> Option.is_none in
let full_msg =
Format.sprintf
"Light mode (%s): %s%s"
(chain_n_block_to_string pgi.chain pgi.block)
msg
(if warn_symbolic && symbolic_block then
Format.sprintf
". Because requested block is symbolic: %s (it has no hash), it could \
be that the different endpoints are mapping this symbolic identifier \
to different concrete blocks. If you are using the 'head' identifier \
(or 'head~1', etc.) in a RPC path, replace it with a concrete hash."
@@ Block_services.to_string pgi.block
else "")
in
let* () = Logger.(emit failing full_msg) in
failwith "%s" full_msg
let get_core (module Light_proto : Light_proto.PROTO_RPCS)
(printer : Tezos_client_base.Client_context.printer)
({endpoints; min_agreement} : Light.sources) =
(module struct
type irmin = {repo : Store.Tree.repo; root : Store.tree}
(** Do not access directly, use [get_irmin] instead *)
let irmin_ref : irmin option ref = ref None
let mk_empty_irmin () : irmin Lwt.t =
let open Lwt_syntax in
let+ repo = Store.Tree.make_repo () in
let root =
Store.Tree.empty (Tezos_context_memory.Context.make_empty_context ())
in
{repo; root}
(* Don't update the irmin ref when looking for key, so as not to add the
empty tree. *)
let get_irmin () : irmin Lwt.t =
match !irmin_ref with
| None -> mk_empty_irmin ()
| Some res -> Lwt.return res
module Storelike = struct
include Store
let find = Store.Tree.find
let find_tree = Store.Tree.find_tree
let unshallow = Tree.unshallow
end
module Get_data = Tezos_context_sigs.Context.With_get_data ((
Storelike : Tezos_context_sigs.Context.Storelike))
(** Returns an {!irmin} value but don't update {!irmin_ref}. We want to
update only when the consensus has been checked, not before! *)
let stage key (tree : (Storelike.tree, bytes) Either.t) =
let open Lwt_syntax in
let* irmin = get_irmin () in
let* root =
match tree with
| Either.Left tree -> Store.Tree.add_tree irmin.root key tree
| Either.Right value -> Store.Tree.add irmin.root key value
in
return_ok {irmin with root}
let rec get_first_merkle_tree chain block key leaf_kind tried_endpoints_rev
remaining_endpoints =
let open Lwt_syntax in
match remaining_endpoints with
| [] -> Lwt.return_none
| ((uri, rpc_context) as hd_endpoint) :: tl_remaining_endpoints -> (
let* proof_opt =
Light_proto.merkle_tree
Proxy.{rpc_context; chain; block; mode = Client}
key
leaf_kind
in
match proof_opt with
| Ok (Some mproof) -> (
let* verification =
Store.verify_tree_proof
mproof
(Get_data.get_data Proof.Raw_context [key])
in
match verification with
| Ok (_, [(k, Some tree)]) when k = key ->
let other_endpoints =
List.rev_append tried_endpoints_rev tl_remaining_endpoints
in
Lwt.return_some (mproof, tree, other_endpoints)
| _ ->
(* Here we skip an endpoint that succeeded with a proof that doesn't map 'key'.
This is okay. *)
get_first_merkle_tree
chain
block
key
leaf_kind
(hd_endpoint :: tried_endpoints_rev)
tl_remaining_endpoints)
| Ok None ->
(* Here we skip an endpoint that succeeded but returned None
This means the endpoint's context does not map 'key'.
This is okay. *)
get_first_merkle_tree
chain
block
key
leaf_kind
(hd_endpoint :: tried_endpoints_rev)
tl_remaining_endpoints
| Error trace ->
let* () =
printer#warning
"Light mode: endpoint %s failed providing merkle tree of key \
%s (%s): %a"
(Uri.to_string uri)
(key_to_string key)
(chain_n_block_to_string chain block)
pp_print_trace
trace
in
get_first_merkle_tree
chain
block
key
leaf_kind
(hd_endpoint :: tried_endpoints_rev)
tl_remaining_endpoints)
(** Returns the Merkle tree of the first successful call to an endpoint
in [endpoints], together with the other [endpoints] so that the caller
is able to ask these endpoints whether they agree Merkle-hash wise.
If [Some (mtree, other_endpoints)] is returned, it is guaranteed that
[List.length endpoints = List.length other_endpoints + 1] *)
let get_first_merkle_tree chain block key leaf_kind :
(Proof.tree Proof.t
* (Storelike.tree, bytes) Either.t
* (Uri.t * Tezos_rpc.Context.simple) list)
option
Lwt.t =
get_first_merkle_tree chain block key leaf_kind [] endpoints
let get key =
let open Lwt_syntax in
let* () = Logger.(emit api_get @@ key_to_string key) in
let* {root; _} = get_irmin () in
Store.Tree.find_tree root key
module Consensus = Light_consensus.Make (Light_proto)
let do_rpc ({chain; block; _} as pgi : Proxy.proxy_getter_input) key =
let open Lwt_result_syntax in
let*! () = Logger.(emit api_do_rpc @@ key_to_string key) in
let*! mproof_and_i_opt =
get_first_merkle_tree chain block key Proof.Raw_context
in
let nb_endpoints = List.length endpoints in
match mproof_and_i_opt with
| None ->
light_failwith pgi
@@ Format.sprintf
"None of the %d endpoints could provide data for key: %s"
nb_endpoints
(key_to_string key)
| Some (mproof, tree, validating_endpoints) -> (
let* staged = stage key tree in
let*! () =
Logger.(
emit
staged_data
(key_to_string key, List.length validating_endpoints))
in
let input : Light_consensus.input =
{printer; min_agreement; chain; block; key; mproof}
in
let*! r = Consensus.consensus input validating_endpoints in
match r with
| false ->
light_failwith pgi ~warn_symbolic:true
@@ Format.sprintf "Consensus cannot be reached for key: %s"
@@ key_to_string key
| true ->
irmin_ref := Some staged ;
return_unit)
end : Proxy.CORE)
Computing file changes ...