123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366(*****************************************************************************)(* *)(* 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. *)(* *)(*****************************************************************************)moduleLocal=Tezos_context_memory.ContextmoduleProof=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?). *)typekind=Get|Memletkind_encoding:kindData_encoding.t=letopenData_encodinginconv(functionGet->true|Mem->false)(functiontrue->Get|false->Mem)boolletpp_kindfmtkind=Format.fprintffmt"%s"(matchkindwithGet->"get"|Mem->"mem")moduleEvents=structincludeInternal_event.Simpleletsection=["proxy_getter"]letpp_key=letpp_sepfmt()=Format.fprintffmt"/"inFormat.pp_print_list~pp_sepFormat.pp_print_stringletcache_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.(liststring))letcache_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.(liststring))letsplit_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.(liststring))("leaf",Data_encoding.(liststring))endletrecraw_context_size=function|Proof.Key_|Cut->0|Dirmap->String.Map.fold(fun_keyvacc->acc+1+raw_context_sizev)map0letrecraw_context_to_tree(raw:Proof.raw_context):Local.treeoptionLwt.t=matchrawwith|Key(bytes:Bytes.t)->Lwt.return(Some(Local.Tree.of_raw(`Valuebytes)))|Cut->Lwt.returnNone|Dirmap->letopenLwt_syntaxinletadd_to_treetree(string,raw_context)=let*u=raw_context_to_treeraw_contextinmatchuwith|None->Lwt.returntree|Someu->Local.Tree.add_treetree[string]uinlet*dir=String.Map.bindingsmap|>List.fold_left_sadd_to_tree(Tezos_context_memory.Context.make_empty_tree())inifLocal.Tree.is_emptydirthenreturn_noneelsereturn_somedirmoduletypeM=sigvalproxy_dir_mem:Proxy.proxy_getter_input->Local.key->booltzresultLwt.tvalproxy_get:Proxy.proxy_getter_input->Local.key->Local.treeoptiontzresultLwt.tvalproxy_mem:Proxy.proxy_getter_input->Local.key->booltzresultLwt.tendtypeproxy_m=(moduleM)typeproxy_builder=|Of_rpcof(Proxy_proto.proto_rpc->proxy_mLwt.t)|Of_data_dirof(Context_hash.t->Tezos_protocol_environment.Proxy_delegate.ttzresultLwt.t)typerpc_context_args={printer:Tezos_client_base.Client_context.printeroption;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;}moduleStringMap=String.Mapletmake_delegate(ctx:rpc_context_args)(proto_rpc:(moduleProxy_proto.PROTO_RPC))(hash:Context_hash.t):Tezos_protocol_environment.Proxy_delegate.ttzresultLwt.t=matchctx.proxy_builderwith|Of_rpcf->letopenLwt_result_syntaxinlet*!(moduleInitial_context)=fproto_rpcinletpgi:Proxy.proxy_getter_input={rpc_context=(ctx.rpc_context:>Tezos_rpc.Context.simple);mode=ctx.mode;chain=ctx.chain;block=ctx.block;}inreturn(modulestructletproxy_dir_mem=Initial_context.proxy_dir_mempgiletproxy_get=Initial_context.proxy_getpgiletproxy_mem=Initial_context.proxy_mempgiend:Tezos_protocol_environment.Proxy_delegate.T)|Of_data_dirf->fhashmoduleTree:Proxy.TREEwithtypet=Local.treewithtypekey=Local.key=structtypet=Local.treetypekey=Local.keyletempty=Local.Tree.empty(Tezos_context_memory.Context.make_empty_context())letget=Local.Tree.find_treeletadd_leaftreekeyraw_context:tProxy.updateLwt.t=letopenLwt_syntaxinlet*tree_opt=raw_context_to_treeraw_contextinlet*updated_tree=matchtree_optwith|None->Lwt.returntree|Somesub_tree->Local.Tree.add_treetreekeysub_treeinLwt.return(Proxy.Valueupdated_tree)endmoduletypeREQUESTS_TREE=sigtypetree=PartialoftreeStringMap.t|Allvalempty:treevaladd:tree->stringlist->treevalfind_opt:tree->stringlist->treeoptionendmoduleRequestsTree:REQUESTS_TREE=structtypetree=PartialoftreeStringMap.t|Allletempty=PartialStringMap.emptyletrecadd(t:tree)(k:stringlist):tree=match(t,k)with|_,[]|All,_->All|Partialmap,k_hd::k_tail->(letsub_t_opt=StringMap.find_optk_hdmapinmatchsub_t_optwith|None->Partial(StringMap.addk_hd(addemptyk_tail)map)|Some(Partial_assub_t)->Partial(StringMap.addk_hd(addsub_tk_tail)map)|SomeAll->t)letrecfind_opt(t:tree)(k:stringlist):treeoption=match(t,k)with|All,_->SomeAll|Partial_,[]->None|Partialmap,k_hd::k_tail->(letsub_t_opt=StringMap.find_optk_hdmapinmatchsub_t_optwith|None->None|SomeAll->SomeAll|Some(Partial_assub_t)->(matchk_tailwith[]->Somesub_t|_->find_optsub_tk_tail))endmoduleCore(T:Proxy.TREEwithtypekey=Local.keyandtypet=Local.tree)(X:Proxy_proto.PROTO_RPC):Proxy.CORE=structletstore=refNone(** Only load the store the first time it is needed *)letlazy_load_store()=match!storewith|None->lete=T.emptyinstore:=Somee;Lwt.returne|Somee->Lwt.returneletgetkey=letopenLwt_syntaxinlet*store=lazy_load_store()inT.getstorekeyletdo_rpc:Proxy.proxy_getter_input->Local.key->unittzresultLwt.t=funpgikey->letopenLwt_result_syntaxinlet*tree=X.do_rpcpgikeyinlet*!current_store=lazy_load_store()in(* Update cache with data obtained *)let*!updated=T.add_leafcurrent_storekeytreein(matchupdatedwithMutation->()|Valuecache'->store:=Somecache');return_unitendmoduleMake(C:Proxy.CORE)(X:Proxy_proto.PROTO_RPC):M=structletrequests=refRequestsTree.emptyletis_allk=matchRequestsTree.find_opt!requestskwithSomeAll->true|_->false(** Handles the application of [X.split_key] to optimize queries. *)letdo_rpc(pgi:Proxy.proxy_getter_input)(kind:kind)(requested_key:Local.key):unittzresultLwt.t=letopenLwt_result_syntaxinletkey_to_get,split=matchkindwith|Mem->(* If the value is not going to be used, don't request a parent *)(requested_key,false)|Get->(matchX.split_keypgi.moderequested_keywith|None->(* There's no splitting for this key *)(requested_key,false)|Some(prefix,_)->(* Splitting triggers: a parent key will be requested *)(prefix,true))inletremember_request()=(* Remember request was done: map [key] to [All] in [!requests]
(see [Proxy_getter.REQUESTS_TREE] mli for further details) *)requests:=RequestsTree.add!requestskey_to_get;return_unitin(* [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) *)ifsplit&&is_allkey_to_getthenreturn_unitelselet*!()=ifsplitthenEvents.(emitsplit_key_triggers(key_to_get,requested_key))elseLwt.return_unitinlet*!r=C.do_rpcpgikey_to_getinmatchrwith|Ok_->remember_request()|Error_whenX.failure_is_permanentrequested_key->remember_request()|Errorerr->(* Don't remember the request, maybe it will succeed in the future *)Lwt.return_errorerr(* [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. *)letgeneric_call:kind->Proxy.proxy_getter_input->Local.key->Local.treeoptiontzresultLwt.t=fun(kind:kind)(pgi:Proxy.proxy_getter_input)(key:Local.key)->letopenLwt_result_syntaxinlet*()=ifis_allkeythen(* 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.(emitcache_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.(emitcache_miss(kind,key))indo_rpcpgikindkeyinLwt_result.ok@@C.getkeyletproxy_getpgikey=generic_callGetpgikeyletproxy_dir_mempgikey=letopenLwt_result_syntaxinlet*tree_opt=generic_callMempgikeyinmatchtree_optwith|None->return_false|Sometree->(matchLocal.Tree.kindtreewith|`Tree->return_true|`Value->return_false)letproxy_mempgikey=letopenLwt_result_syntaxinlet*tree_opt=generic_callMempgikeyinmatchtree_optwith|None->return_false|Sometree->(matchLocal.Tree.kindtreewith|`Tree->return_false|`Value->return_true)endmoduleMakeProxy(X:Proxy_proto.PROTO_RPC):M=Make(Core(Tree)(X))(X)moduleInternal=structmoduleTree=Treeletraw_context_to_tree=raw_context_to_treeend