123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320(*****************************************************************************)(* *)(* 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. *)(* *)(*****************************************************************************)moduleCache_costs=structmoduleS=Saturation_repr(* Computed by typing the contract
"{parameter unit; storage unit; code FAILWITH}"
and evaluating
[(8 * Obj.reachable_words (Obj.repr typed_script))]
where [typed_script] is of type [ex_script] *)letminimal_size_of_typed_contract_in_bytes=688letapproximate_cardinalbytes=S.safe_int(bytes/minimal_size_of_typed_contract_in_bytes)letlog2x=S.safe_int(1+S.numbitsx)letcache_update_constant=S.safe_int600letcache_update_coeff=S.safe_int43(* Cost of calling [Environment_cache.update]. *)letcache_update~cache_size_in_bytes=letapprox_card=approximate_cardinalcache_size_in_bytesinGas_limit_repr.atomic_step_costS.(addcache_update_constant(mulcache_update_coeff(log2approx_card)))(* Cost of calling [Environment_cache.find].
This overapproximates [cache_find] slightly. *)letcache_find=cache_updateendtypeindex=inttypesize=inttypeidentifier=stringtypenamespace=stringtypecache_nonce=Bytes.tletcompare_namespace=Compare.String.comparetypeinternal_identifier={namespace:namespace;id:identifier}letseparator='@'letsanitizenamespace=ifString.containsnamespaceseparatortheninvalid_arg(Format.asprintf"Invalid cache namespace: '%s'. Character %c is forbidden."namespaceseparator)elsenamespaceletcreate_namespace=sanitizeletstring_of_internal_identifier{namespace;id}=namespace^String.make1separator^idletinternal_identifier_of_stringraw=matchString.index_optrawseparatorwith|None->assertfalse|Someindex->{(* We do not need to call sanitize here since we stop at the first '@'
from index 0. It is a guarantee that there is no '@' between 0 and
(index - 1 ). *)namespace=String.subraw0index;id=(letdelim_idx=index+1inString.subrawdelim_idx(String.lengthraw-delim_idx));}letinternal_identifier_of_keykey=letraw=Raw_context.Cache.identifier_of_keykeyininternal_identifier_of_stringrawletkey_of_internal_identifier~cache_indexidentifier=letraw=string_of_internal_identifieridentifierinRaw_context.Cache.key_of_identifier~cache_indexrawletmake_key=letnamespaces=ref[]infun~cache_index~namespace->ifList.mem~equal:String.equalnamespace!namespacestheninvalid_arg(Format.sprintf"Cache key namespace %s already exist."namespace)else(namespaces:=namespace::!namespaces;fun~id->letidentifier={namespace;id}inkey_of_internal_identifier~cache_indexidentifier)moduleNamespaceMap=Map.Make(structtypet=namespaceletcompare=compare_namespaceend)typepartial_key_handler=Raw_context.t->string->Context.Cache.valuetzresultLwt.tletvalue_of_key_handlers:partial_key_handlerNamespaceMap.tref=refNamespaceMap.emptymoduleAdmin=structincludeRaw_context.Cacheletfuture_cache_expectation?blocks_before_activationctxt~time_in_blocks=lettime_in_blocks'=Int32.of_inttime_in_blocksinletblocks_per_voting_period=Int32.(mul(Constants_storage.cycles_per_voting_periodctxt)(Constants_storage.blocks_per_cyclectxt))in(matchblocks_before_activationwith|None->Voting_period_storage.blocks_before_activationctxt|Someblock->return_someblock)>>=?function|SomeblockwhenCompare.Int32.((Compare.Int32.(block>=0l)&&block<=time_in_blocks')||blocks_per_voting_period<time_in_blocks')->(*
At each protocol activation, the cache is clear.
For this reason, if the future block considered for the
prediction is after the activation, the predicted cache
is set to empty. That way, the predicted gas consumption
is guaranteed to be an overapproximation of the actual
gas consumption.
This function implicitly assumes that [time_in_blocks]
is less than [blocks_per_voting_period]. (The default
value in the simulate_operation RPC is set to 3, and
therefore satisfies this condition.) As a defensive
protection, we clear the cache if this assumption is
not satisfied with user-provided values. Notice that
high user-provided values for [time_in_blocks] do not
make much sense as the cache prediction only works for
blocks in the short-term future.
*)return@@Raw_context.Cache.clearctxt|_->return@@Raw_context.Cache.future_cache_expectationctxt~time_in_blocksletlist_keyscontext~cache_index=Raw_context.Cache.list_keyscontext~cache_indexletkey_rankcontextkey=Raw_context.Cache.key_rankcontextkeyletvalue_of_keyctxtkey=(* [value_of_key] is a maintenance operation: it is typically run
when a node reboots. For this reason, this operation is not
carbonated. *)letctxt=Raw_context.set_gas_unlimitedctxtinlet{namespace;id}=internal_identifier_of_keykeyinmatchNamespaceMap.findnamespace!value_of_key_handlerswith|Somevalue_of_key->value_of_keyctxtid|None->failwith(Format.sprintf"No handler for key `%s%c%s'"namespaceseparatorid)endmoduletypeCLIENT=sigvalcache_index:intvalnamespace:namespacetypecached_valuevalvalue_of_identifier:Raw_context.t->identifier->cached_valuetzresultLwt.tendmoduletypeINTERFACE=sigtypecached_valuevalupdate:Raw_context.t->identifier->(cached_value*int)option->Raw_context.ttzresultvalfind:Raw_context.t->identifier->cached_valueoptiontzresultLwt.tvallist_identifiers:Raw_context.t->(identifier*int)listvalidentifier_rank:Raw_context.t->identifier->intoptionvalsize:Raw_context.t->sizevalsize_limit:Raw_context.t->sizeendletregister_exn(typecvalue)(moduleC:CLIENTwithtypecached_value=cvalue):(moduleINTERFACEwithtypecached_value=cvalue)=ifCompare.Int.(C.cache_index<0)||Compare.Int.(Constants_repr.cache_layout_size<=C.cache_index)theninvalid_arg"Cache index is invalid";letmk=make_key~cache_index:C.cache_index~namespace:C.namespacein(modulestructtypecached_value=C.cached_valuetypeAdmin.value+=Kofcached_valuelet()=letvoictxti=C.value_of_identifierctxti>>=?funv->return(Kv)invalue_of_key_handlers:=NamespaceMap.addC.namespacevoi!value_of_key_handlersletsizectxt=Option.value~default:max_int@@Admin.cache_sizectxt~cache_index:C.cache_indexletsize_limitctxt=Option.value~default:0@@Admin.cache_size_limitctxt~cache_index:C.cache_indexletupdatectxtidv=letcache_size_in_bytes=sizectxtinRaw_context.consume_gasctxt(Cache_costs.cache_update~cache_size_in_bytes)>|?functxt->letv=Option.map(fun(v,size)->(Kv,size))vinAdmin.updatectxt(mk~id)vletfindctxtid=letcache_size_in_bytes=sizectxtinRaw_context.consume_gasctxt(Cache_costs.cache_find~cache_size_in_bytes)>>?=functxt->Admin.findctxt(mk~id)>>=function|None->returnNone|Some(Kv)->return(Somev)|_->(* This execution path is impossible because all the keys of
C's namespace (which is unique to C) are constructed with
[K]. This [assert false] could have been pushed into the
environment in exchange for extra complexity. The
argument that justifies this [assert false] seems
simple enough to keep the current design though. *)assertfalseletlist_identifiersctxt=Admin.list_keysctxt~cache_index:C.cache_index|>function|None->(* `cache_index` is valid. *)assertfalse|Somelist->List.filter_map(fun(key,age)->let{namespace;id}=internal_identifier_of_keykeyinifString.equalnamespaceC.namespacethenSome(id,age)elseNone)listletidentifier_rankctxtid=Admin.key_rankctxt(mk~id)end)letcache_nonce_from_block_header(shell:Block_header.shell_header)contents:cache_nonce=letopenBlock_header_reprinletshell:Block_header.shell_header={level=0l;proto_level=0;predecessor=shell.predecessor;timestamp=Time.of_seconds0L;validation_passes=0;operations_hash=shell.operations_hash;fitness=[];context=Context_hash.zero;}inletcontents={contentswithpayload_hash=Block_payload_hash.zero;proof_of_work_nonce=Bytes.makeConstants_repr.proof_of_work_nonce_size'0';}inletprotocol_data={signature=Signature.zero;contents}inletx={shell;protocol_data}inBlock_hash.to_bytes(hashx)