package octez-shell-libs

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file proxy_getter.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2020-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 Local = Tezos_context_memory.Context
module Proof = Tezos_context_sigs.Context.Proof_types

(** The kind of RPC request: is it a GET (i.e. is it loading data?) or
    is it only a MEMbership request (i.e. is the key associated to data?). *)
type kind = Get | Mem

let kind_encoding : kind Data_encoding.t =
  let open Data_encoding in
  conv
    (function Get -> true | Mem -> false)
    (function true -> Get | false -> Mem)
    bool

let pp_kind fmt kind =
  Format.fprintf fmt "%s" (match kind with Get -> "get" | Mem -> "mem")

module Events = struct
  include Internal_event.Simple

  let section = ["proxy_getter"]

  let pp_key =
    let pp_sep fmt () = Format.fprintf fmt "/" in
    Format.pp_print_list ~pp_sep Format.pp_print_string

  let cache_hit =
    declare_2
      ~section
      ~name:"cache_hit"
      ~msg:"Cache hit ({kind}): ({key})"
      ~level:Debug
      ~pp1:pp_kind
      ~pp2:pp_key
      ("kind", kind_encoding)
      ("key", Data_encoding.(list string))

  let cache_miss =
    declare_2
      ~section
      ~name:"cache_miss"
      ~msg:"Cache miss ({kind}): ({key})"
      ~level:Debug
      ~pp1:pp_kind
      ~pp2:pp_key
      ("kind", kind_encoding)
      ("key", Data_encoding.(list string))

  let split_key_triggers =
    declare_2
      ~section
      ~level:Debug
      ~name:"split_key_triggers"
      ~msg:"split_key heuristic triggers, getting {parent} instead of {leaf}"
      ~pp1:pp_key
      ~pp2:pp_key
      ("parent", Data_encoding.(list string))
      ("leaf", Data_encoding.(list string))
end

let rec raw_context_size = function
  | Proof.Key _ | Cut -> 0
  | Dir map ->
      String.Map.fold (fun _key v acc -> acc + 1 + raw_context_size v) map 0

let rec raw_context_to_tree (raw : Proof.raw_context) : Local.tree option Lwt.t
    =
  match raw with
  | Key (bytes : Bytes.t) -> Lwt.return_some (Local.Tree.of_raw (`Value bytes))
  | Cut -> Lwt.return_none
  | Dir map ->
      let open Lwt_syntax in
      let add_to_tree tree (string, raw_context) =
        let* u = raw_context_to_tree raw_context in
        match u with
        | None -> Lwt.return tree
        | Some u -> Local.Tree.add_tree tree [string] u
      in
      let* dir =
        String.Map.bindings map
        |> List.fold_left_s
             add_to_tree
             (Tezos_context_memory.Context.make_empty_tree ())
      in
      if Local.Tree.is_empty dir then return_none else return_some dir

module type M = sig
  val proxy_dir_mem :
    Proxy.proxy_getter_input -> Local.key -> bool tzresult Lwt.t

  val proxy_get :
    Proxy.proxy_getter_input -> Local.key -> Local.tree option tzresult Lwt.t

  val proxy_mem : Proxy.proxy_getter_input -> Local.key -> bool tzresult Lwt.t
end

type proxy_m = (module M)

type proxy_builder =
  | Of_rpc of (Proxy_proto.proto_rpc -> proxy_m Lwt.t)
  | Of_data_dir of
      (Context_hash.t ->
      Tezos_protocol_environment.Proxy_delegate.t tzresult Lwt.t)

type rpc_context_args = {
  printer : Tezos_client_base.Client_context.printer option;
  proxy_builder : proxy_builder;
  rpc_context : Tezos_rpc.Context.generic;
  mode : Proxy.mode;
  chain : Tezos_shell_services.Block_services.chain;
  block : Tezos_shell_services.Block_services.block;
}

module StringMap = String.Map

let make_delegate (ctx : rpc_context_args)
    (proto_rpc : (module Proxy_proto.PROTO_RPC)) (hash : Context_hash.t) :
    Tezos_protocol_environment.Proxy_delegate.t tzresult Lwt.t =
  match ctx.proxy_builder with
  | Of_rpc f ->
      let open Lwt_result_syntax in
      let*! (module Initial_context) = f proto_rpc in
      let pgi : Proxy.proxy_getter_input =
        {
          rpc_context = (ctx.rpc_context :> Tezos_rpc.Context.simple);
          mode = ctx.mode;
          chain = ctx.chain;
          block = ctx.block;
        }
      in
      return
        (module struct
          let proxy_dir_mem = Initial_context.proxy_dir_mem pgi

          let proxy_get = Initial_context.proxy_get pgi

          let proxy_mem = Initial_context.proxy_mem pgi
        end : Tezos_protocol_environment.Proxy_delegate.T)
  | Of_data_dir f -> f hash

module Tree : Proxy.TREE with type t = Local.tree with type key = Local.key =
struct
  type t = Local.tree

  type key = Local.key

  let empty =
    Local.Tree.empty (Tezos_context_memory.Context.make_empty_context ())

  let get = Local.Tree.find_tree

  let add_leaf tree key raw_context : t Proxy.update Lwt.t =
    let open Lwt_syntax in
    let* tree_opt = raw_context_to_tree raw_context in
    let* updated_tree =
      match tree_opt with
      | None -> Lwt.return tree
      | Some sub_tree -> Local.Tree.add_tree tree key sub_tree
    in
    Lwt.return (Proxy.Value updated_tree)
end

module type REQUESTS_TREE = sig
  type tree = Partial of tree StringMap.t | All

  val empty : tree

  val add : tree -> string list -> tree

  val find_opt : tree -> string list -> tree option
end

module RequestsTree : REQUESTS_TREE = struct
  type tree = Partial of tree StringMap.t | All

  let empty = Partial StringMap.empty

  let rec add (t : tree) (k : string list) : tree =
    match (t, k) with
    | _, [] | All, _ -> All
    | Partial map, k_hd :: k_tail -> (
        let sub_t_opt = StringMap.find_opt k_hd map in
        match sub_t_opt with
        | None -> Partial (StringMap.add k_hd (add empty k_tail) map)
        | Some (Partial _ as sub_t) ->
            Partial (StringMap.add k_hd (add sub_t k_tail) map)
        | Some All -> t)

  let rec find_opt (t : tree) (k : string list) : tree option =
    match (t, k) with
    | All, _ -> Some All
    | Partial _, [] -> None
    | Partial map, k_hd :: k_tail -> (
        let sub_t_opt = StringMap.find_opt k_hd map in
        match sub_t_opt with
        | None -> None
        | Some All -> Some All
        | Some (Partial _ as sub_t) -> (
            match k_tail with [] -> Some sub_t | _ -> find_opt sub_t k_tail))
end

module Core
    (T : Proxy.TREE with type key = Local.key and type t = Local.tree)
    (X : Proxy_proto.PROTO_RPC) : Proxy.CORE = struct
  let store = ref None

  (** Only load the store the first time it is needed *)
  let lazy_load_store () =
    match !store with
    | None ->
        let e = T.empty in
        store := Some e ;
        Lwt.return e
    | Some e -> Lwt.return e

  let get key =
    let open Lwt_syntax in
    let* store = lazy_load_store () in
    T.get store key

  let do_rpc : Proxy.proxy_getter_input -> Local.key -> unit tzresult Lwt.t =
   fun pgi key ->
    let open Lwt_result_syntax in
    let* tree = X.do_rpc pgi key in
    let*! current_store = lazy_load_store () in
    (* Update cache with data obtained *)
    let*! updated = T.add_leaf current_store key tree in
    (match updated with Mutation -> () | Value cache' -> store := Some cache') ;
    return_unit
end

module Make (C : Proxy.CORE) (X : Proxy_proto.PROTO_RPC) : M = struct
  let requests = ref RequestsTree.empty

  let is_all k =
    match RequestsTree.find_opt !requests k with Some All -> true | _ -> false

  (** Handles the application of [X.split_key] to optimize queries. *)
  let do_rpc (pgi : Proxy.proxy_getter_input) (kind : kind)
      (requested_key : Local.key) : unit tzresult Lwt.t =
    let open Lwt_result_syntax in
    let key_to_get, split =
      match kind with
      | Mem ->
          (* If the value is not going to be used, don't request a parent *)
          (requested_key, false)
      | Get -> (
          match X.split_key pgi.mode requested_key with
          | None ->
              (* There's no splitting for this key *)
              (requested_key, false)
          | Some (prefix, _) ->
              (* Splitting triggers: a parent key will be requested *)
              (prefix, true))
    in
    let remember_request () =
      (* Remember request was done: map [key] to [All] in [!requests]
         (see [Proxy_getter.REQUESTS_TREE] mli for further details) *)
      requests := RequestsTree.add !requests key_to_get ;
      return_unit
    in
    (* [is_all] has been checked (by the caller: [generic_call])
       for the key received as parameter. Hence it only makes sense
       to check it if a parent key is being retrieved ('split' = true
       and hence 'key' here differs from the key received as parameter) *)
    if split && is_all key_to_get then return_unit
    else
      let*! () =
        if split then
          Events.(emit split_key_triggers (key_to_get, requested_key))
        else Lwt.return_unit
      in
      let*! r = C.do_rpc pgi key_to_get in
      match r with
      | Ok _ -> remember_request ()
      | Error _ when X.failure_is_permanent requested_key -> remember_request ()
      | Error err ->
          (* Don't remember the request, maybe it will succeed in the future *)
          Lwt.return_error err

  (* [generic_call] and [do_rpc] above go hand in hand. [do_rpc] takes
     care of performing the RPC call and updating [cache].
     [generic_call] calls [do_rpc] to make sure the cache is filled, and
     then queries the cache to return the desired value.
     Having them separate allows to avoid mixing the logic of
     [X.split_key] (confined to [do_rpc]) and the logic of getting
     the key's value. *)
  let generic_call :
      kind ->
      Proxy.proxy_getter_input ->
      Local.key ->
      Local.tree option tzresult Lwt.t =
   fun (kind : kind) (pgi : Proxy.proxy_getter_input) (key : Local.key) ->
    let open Lwt_result_syntax in
    let* () =
      if is_all key then
        (* This exact request was done already.
           So data was obtained already. Note that this does not imply
           that this function will return [Some] (maybe the node doesn't
           map this key). *)
        Lwt_result.ok @@ Events.(emit cache_hit (kind, key))
      else
        (* This exact request was NOT done already (either a longer request
           was done or no related request was done at all).
           An RPC MUST be done. *)
        let*! () = Events.(emit cache_miss (kind, key)) in
        do_rpc pgi kind key
    in
    Lwt_result.ok @@ C.get key

  let proxy_get pgi key = generic_call Get pgi key

  let proxy_dir_mem pgi key =
    let open Lwt_result_syntax in
    let* tree_opt = generic_call Mem pgi key in
    match tree_opt with
    | None -> return_false
    | Some tree -> (
        match Local.Tree.kind tree with
        | `Tree -> return_true
        | `Value -> return_false)

  let proxy_mem pgi key =
    let open Lwt_result_syntax in
    let* tree_opt = generic_call Mem pgi key in
    match tree_opt with
    | None -> return_false
    | Some tree -> (
        match Local.Tree.kind tree with
        | `Tree -> return_false
        | `Value -> return_true)
end

module MakeProxy (X : Proxy_proto.PROTO_RPC) : M = Make (Core (Tree) (X)) (X)

module Internal = struct
  module Tree = Tree

  let raw_context_to_tree = raw_context_to_tree
end
OCaml

Innovation. Community. Security.