123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2023 Nomadic Labs <contact@nomadic-labs.com> *)(* Copyright (c) 2023 Functori <contact@functori.com> *)(* Copyright (c) 2023 Marigold <contact@marigold.dev> *)(* *)(* 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. *)(* *)(*****************************************************************************)typemode=Sequencerof{kernel:string;preimage_dir:string}|ProxymoduleParameters=structtypepersistent_state={arguments:stringlist;mutablepending_ready:unitoptionLwt.ulist;mode:mode;data_dir:string;devmode:bool;rpc_addr:string;rpc_port:int;rollup_node_endpoint:string;runner:Runner.toption;}typesession_state={mutableready:bool}letbase_default_name="evm_node"letdefault_colors=Log.Color.[|FG.magenta|]endopenParametersincludeDaemon.Make(Parameters)letconnection_arguments~devmode?rpc_addr?rpc_port()=letopenCli_arginletrpc_port=matchrpc_portwithNone->Port.fresh()|Someport->portin(["--rpc-port";string_of_intrpc_port]@optional_arg"rpc-addr"Fun.idrpc_addr@optional_switch"devmode"devmode,Option.value~default:"127.0.0.1"rpc_addr,rpc_port)lettrigger_readysc_nodevalue=letpending=sc_node.persistent_state.pending_readyinsc_node.persistent_state.pending_ready<-[];List.iter(funpending->Lwt.wakeup_laterpendingvalue)pendingletset_readyevm_node=(matchevm_node.statuswith|Not_running->()|Runningstatus->status.session_state.ready<-true);trigger_readyevm_node(Some())letevent_ready_name="evm_node_is_ready.v0"lethandle_event(evm_node:t){name;value=_;timestamp=_}=ifname=event_ready_namethenset_readyevm_nodeelse()letcheck_eventevm_nodenamepromise=let*result=promiseinmatchresultwith|None->raise(Terminated_before_event{daemon=evm_node.name;event=name;where=None})|Somex->returnxletwait_for_readyevm_node=matchevm_node.statuswith|Running{session_state={ready=true;_};_}->unit|Not_running|Running{session_state={ready=false;_};_}->letpromise,resolver=Lwt.task()inevm_node.persistent_state.pending_ready<-resolver::evm_node.persistent_state.pending_ready;check_eventevm_nodeevent_ready_namepromiseletcreate?runner?(mode=Proxy)?data_dir~devmode?rpc_addr?rpc_portrollup_node_endpoint=letarguments,rpc_addr,rpc_port=connection_arguments~devmode ?rpc_addr?rpc_port()inletname=fresh_name()inletdata_dir=matchdata_dirwithNone->Temp.dirname|Somedir->dirinletevm_node=create~path:(Uses.pathConstant.octez_evm_node)~name{arguments;pending_ready=[];mode;data_dir;devmode;rpc_addr;rpc_port;rollup_node_endpoint;runner;}inon_eventevm_node(handle_eventevm_node);evm_nodeletcreate?runner?mode?data_dir?(devmode=false)?rpc_addr?rpc_portrollup_node=create?mode?runner?data_dir~devmode?rpc_addr?rpc_portrollup_nodeletdata_direvm_node=["--data-dir";evm_node.persistent_state.data_dir]letrun_argsevm_node=letshared_args=data_direvm_node@evm_node.persistent_state.argumentsinletmode_args=matchevm_node.persistent_state.modewith|Proxy->["run";"proxy";"with";"endpoint";evm_node.persistent_state.rollup_node_endpoint;]|Sequencer{kernel;preimage_dir}->["run";"sequencer";"with";"endpoint";evm_node.persistent_state.rollup_node_endpoint;"--kernel";kernel;"--preimage-dir";preimage_dir;]inmode_args@shared_argsletrunevm_node=let*()=runevm_node{ready=false}(run_argsevm_node)inlet*()=wait_for_readyevm_nodeinunitletspawn_commandevm_nodeargs=Process.spawn?runner:evm_node.persistent_state.runner(Uses.pathConstant.octez_evm_node)@@argsletspawn_runevm_node=spawn_commandevm_node(run_argsevm_node)letendpoint(evm_node:t)=Format.sprintf"http://%s:%d"evm_node.persistent_state.rpc_addrevm_node.persistent_state.rpc_portletinit?runner?mode?data_dir?(devmode=false)?rpc_addr?rpc_portrollup_node=letevm_node=create?runner?mode?data_dir~devmode?rpc_addr?rpc_portrollup_nodeinlet*()=runevm_nodeinreturnevm_nodetyperequest={method_:string;parameters:JSON.u}letrequest_to_JSON{method_;parameters}:JSON.u=`O[("jsonrpc",`String"2.0");("method",`Stringmethod_);("params",parameters);("id",`String"0");]letbuild_requestrequest=request_to_JSONrequest|>JSON.annotate~origin:"evm_node"letbatch_requestsrequests=`A(List.maprequest_to_JSONrequests)|>JSON.annotate~origin:"evm_node"(* We keep both encoding (with a single object or an array of objects) and both
function on purpose, to ensure both encoding are supported by the server. *)letcall_evm_rpcevm_noderequest=letendpoint=endpointevm_nodeinCurl.postendpoint(build_requestrequest)|>Runnable.runletbatch_evm_rpcevm_noderequests=letendpoint=endpointevm_nodeinCurl.postendpoint(batch_requestsrequests)|>Runnable.runletextract_resultjson=JSON.(json|->"result")letextract_error_messagejson=JSON.(json|->"error"|->"message")letfetch_contract_codeevm_nodecontract_address=let*code=call_evm_rpcevm_node{method_="eth_getCode";parameters=`A[`Stringcontract_address;`String"latest"];}inreturn(extract_resultcode|>JSON.as_string)typetxpool_slot={address:string;transactions:(int64*JSON.t)list}lettxpool_contentevm_node=let*txpool=call_evm_rpcevm_node{method_="txpool_content";parameters=`A[]}inLog.info"Result: %s"(JSON.encodetxpool);lettxpool=extract_resulttxpoolinletparsefield=letopenJSONinletpool=txpool|->fieldin(* `|->` returns `Null if the field does not exists, and `Null is
interpreted as the empty list by `as_object`. As such, we must ensure the
field exists. *)ifis_nullpoolthenTest.fail"%s must exists"fieldelsepool|>as_object|>List.map(fun(address,transactions)->lettransactions=transactions|>as_object|>List.map(fun(nonce,tx)->(Int64.of_stringnonce,tx))in{address;transactions})inreturn(parse"pending",parse"queued")