123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2023 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. *)(* *)(*****************************************************************************)typeerror+=|RPC_Process_Port_already_in_useofP2p_point.Id.tlist|Missing_socket_dirlet()=register_error_kind`Permanent~id:"rpc_process.main.process_port_already_in_use"~title:"Cannot start RPC process: RPC port already in use"~description:"Another octez RPC process is probably running on the same RPC port."~pp:(funppfaddrlist->Format.fprintfppf"Another octez RPC process is probably running on one of these \
addresses (%a). Please choose another RPC port."(Format.pp_print_listP2p_point.Id.pp)addrlist)Data_encoding.(obj1(req"addrlist"(listP2p_point.Id.encoding)))(function|RPC_Process_Port_already_in_useaddrlist->Someaddrlist|_->None)(funaddrlist->RPC_Process_Port_already_in_useaddrlist);register_error_kind`Permanent~id:"rpc_process.main.missing_socket_dir"~title:"Cannot start RPC process: missing socket dir"~description:"Cannot start RPC process without socket dir."~pp:(funppf()->Format.fprintfppf"The path to the communication socket is missing. Use the --socket-dir \
option to specify it when running the RPC process. ")Data_encoding.empty(functionMissing_socket_dir->Some()|_->None)(fun()->Missing_socket_dir)(* Add default accepted CORS headers *)letsanitize_cors_headers~defaultheaders=List.mapString.lowercase_asciiheaders|>String.Set.of_list|>String.Set.(union(of_listdefault))|>String.Set.elementsletlaunch_rpc_server(params:Parameters.t)(addr,port)=letopenConfig_fileinletopenLwt_result_syntaxinletmedia_types=params.config.rpc.media_typeinlet*!acl_policy=RPC_server.Acl.resolve_domain_namesparams.config.rpc.aclinlethost=Ipaddr.V6.to_stringaddrinletmode=matchparams.config.rpc.tlswith|None->`TCP(`Portport)|Some{cert;key}->`TLS(`Crt_file_pathcert,`Key_file_pathkey,`No_password,`Portport)inletacl=letopenRPC_server.Aclinfind_policyacl_policy(Ipaddr.V6.to_stringaddr,Someport)|>Option.value_f~default:(fun()->defaultaddr)inlet*!()=Rpc_process_event.(emitstarting_rpc_server)(host,port,params.config.rpc.tls<>None,RPC_server.Acl.policy_typeacl)inletcors_headers=sanitize_cors_headers~default:["Content-Type"]params.config.rpc.cors_headersinletcors=Resto_cohttp.Cors.{allowed_origins=params.config.rpc.cors_origins;allowed_headers=cors_headers;}inletdir=Directory.build_rpc_directoryparams.node_versionparams.configinletserver=RPC_server.init_server~cors~acl~media_types:(Media_type.Command_line.of_command_linemedia_types)dirinletcallback=Forward_handler.callback~aclserverparams.rpc_comm_socket_pathinLwt.catch(fun()->let*!()=RPC_server.launch~hostserver~callback~max_active_connections:params.config.rpc.max_active_rpc_connectionsmodeinreturnserver)(function(* FIXME: https://gitlab.com/tezos/tezos/-/issues/1312
This exception seems to be unreachable.
*)|Unix.Unix_error(Unix.EADDRINUSE,"bind","")->tzfail(RPC_Process_Port_already_in_use[(addr,port)])|exn->fail_with_exnexn)letinit_rpcparameters=letopenLwt_result_syntaxinlet*server=let*p2p_point=matchparameters.Parameters.config.Config_file.rpc.listen_addrswith|[addr]->Config_file.resolve_rpc_listening_addrsaddr|_->(* We assume that the config contains only one listening
address. This should hold thanks to the `init_rpc`
function from `bin_node/Node_run_command`. *)assertfalseinmatchp2p_pointwith|[point]->launch_rpc_serverparameterspoint|_->(* Same as above: only one p2p_point is expected here. *)assertfalsein(* Add a cleanup call back to shutdown the RPC_server gracefully
when an exit signal is received. *)let(_ccid:Lwt_exit.clean_up_callback_id)=Lwt_exit.register_clean_up_callback~loc:__LOC__(fun_->RPC_server.shutdownserver)inreturn_unitletget_init_socket_pathsocket_dirpid=letfilename=Format.sprintf"init-rpc-socket-%d"pidinFilename.concatsocket_dirfilename(* Magic bytes used for the external RPC process handshake. *)letsocket_magic=Bytes.of_string"TEZOS_RPC_SERVER_MAGIC_0"letcreate_init_socketsocket_dir=letopenLwt_result_syntaxinlet*socket_dir=matchsocket_dirwith|Somesd->returnsd|None->tzfailMissing_socket_dirinletpid=Unix.getpid()inletinit_socket_path=get_init_socket_pathsocket_dirpidinlet*init_socket_fd=Socket.connect(Unixinit_socket_path)in(* Unlink the socket as soon as both sides have opened it.*)let*!()=Lwt_unix.unlinkinit_socket_pathinlet*()=Socket.handshakeinit_socket_fdsocket_magicinreturninit_socket_fdletrunsocket_dir=letopenLwt_result_syntaxinlet*init_socket_fd=create_init_socketsocket_dirinlet*parameters=Socket.recvinit_socket_fdParameters.parameters_encodinginlet*!()=Tezos_base_unix.Internal_event_unix.init~config:parameters.Parameters.internal_events()inlet*()=init_rpcparametersin(* Send the params ack as synchronization barrier for the init_rpc
phase. *)let*()=Socket.sendinit_socket_fdData_encoding.unit()inlet*!()=Lwt_unix.closeinit_socket_fdinLwt_utils.never_ending()letprocesssocket_dir=letopenLwt_result_syntaxinletmain_promise=Lwt.catch(fun()->runsocket_dir)(functionexn->fail_with_exnexn)inLwt_main.run(let*!r=Lwt_exit.wrap_and_exitmain_promiseinmatchrwith|Ok()->let*!_=Lwt_exit.exit_and_wait0inLwt.return(`Ok())|Errorerr->let*!_=Lwt_exit.exit_and_wait1inLwt.return@@`Error(false,Format.asprintf"%a"pp_print_traceerr))moduleTerm=structletsocket_dir=letopenCmdlinerinletdoc="Socket directory to communicate with node process"inArg.(value&opt(somestring)None&info~docs:Shared_arg.Manpage.misc_section~doc["socket-dir"])letterm=Cmdliner.Term.(ret(constprocess$socket_dir))endmoduleManpage=structletcommand_description="The $(b, octez-rpc-process) starts the RPC process that aims to serve as \
the default endpoint for RPC queries. This server may communicate with an \
Octez node."letdescription=[`S"DESCRIPTION";`Pcommand_description]letman=descriptionletinfo=Cmdliner.Cmd.info~doc:"Run the Octez rpc process"~man"octez-rpc-process"endletcmd=Cmdliner.Cmd.vManpage.infoTerm.term