Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file baking_vdf.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2022 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. *)(* *)(*****************************************************************************)openProtocolopenAlpha_contextopenClient_baking_blocksmoduleEvents=Baking_events.VDFtypevdf_solution=Seed_repr.vdf_solutiontypevdf_setup=Seed_repr.vdf_setuptypeforked_process={pid:int;ch_in:Lwt_io.input_channel}typestatus=|Not_started|Startedofvdf_setup*forked_process|Finishedofvdf_setup*vdf_solution|Injected|Invalidtype'astate={cctxt:Protocol_client_context.full;constants:Constants.t;mutableblock_stream:(block_info,'a)resultLwt_stream.t;mutablestream_stopper:Tezos_rpc.Context.stopperoption;mutablecycle:Cycle.toption;mutablecomputation_status:status;}letinit_block_stream_with_stoppercctxtchain=Client_baking_blocks.monitor_headscctxt~next_protocols:(Some[Protocol.hash])chainletstop_block_streamstate=Option.iter(funstopper->stopper();state.stream_stopper<-None)state.stream_stopperletemit_with_levelmsglevel=letlevel_i32=Raw_level.to_int32levelinEvents.(emitvdf_info)(Printf.sprintf"%s (level %ld)"msglevel_i32)letemit_revelation_not_injectedcycle=letopenLwt_result_syntaxinlet*!()=Events.(emitvdf_info)(Printf.sprintf"VDF revelation was NOT injected for cycle %ld"(Cycle.to_int32cycle))inreturn_unitletlog_errors_and_continue~namep=letopenLwt_syntaxinlet*pinmatchpwith|Ok()->return_unit|Errorerrs->Events.(emitvdf_daemon_error)(name,errs)letget_seed_computationcctxtchain_idhash=letchain=`Hashchain_idinletblock=`Hash(hash,0)inAlpha_services.Seed_computation.getcctxt(chain,block)letget_level_infocctxtlevel=letopenLwt_result_syntaxinletlevel=Raw_level.to_int32levelinlet*{protocol_data={level_info;_};_}=Protocol_client_context.Alpha_block_services.metadatacctxt~chain:cctxt#chain~block:(`Levellevel)()inreturnlevel_infoletis_in_nonce_revelation_stageconstants(level:Level.t)=letopenLwt_result_syntaxinlet{Constants.parametric={nonce_revelation_threshold;_};_}=constantsinreturn(Vdf_helpers.is_in_nonce_revelation_stage~nonce_revelation_threshold~level)(* Checks if the VDF setup saved in the state is equal to the one computed
from a seed *)leteq_vdf_setupvdf_setupseed_discriminantseed_challenge=letopenEnvironment.Vdfinletsaved_discriminant,saved_challenge=vdf_setupinletdiscriminant,challenge=Seed.generate_vdf_setup~seed_discriminant~seed_challengeinBytes.equal(discriminant_to_bytesdiscriminant)(discriminant_to_bytessaved_discriminant)&&Bytes.equal(challenge_to_byteschallenge)(challenge_to_bytessaved_challenge)(* Forge the VDF revelation operation and inject it if:
* - it is correct wrt the VDF setup for the current cycle
* - we are still in the VDF revelation stage
* If successful or if the seed no longer needs to be injected,
* update the computation status. *)letinject_vdf_revelationcctxtstatesetupsolutionchain_idhash(level_info:Level.t)=letopenLwt_result_syntaxinletchain=`Hashchain_idinletblock=`Hash(hash,0)inletlevel=level_info.levelinlet*seed_computation=get_seed_computationcctxtchain_idhashinmatchseed_computationwith|Vdf_revelation_stage{seed_discriminant;seed_challenge}->ifeq_vdf_setupsetupseed_discriminantseed_challengethen(let*op_bytes=Plugin.RPC.Forge.vdf_revelationcctxt(chain,block)~branch:hash~solution()inletop_bytes=Tezos_crypto.Signature.V_latest.(concatop_byteszero)inlet*op_hash=Shell_services.Injection.operationcctxt~chainop_bytesin(* If injection is successful, update the status to [Injected]. *)state.computation_status<-Injected;let*!()=Events.(emitvdf_revelation_injected)(Cycle.to_int32level_info.cycle,Chain_services.to_stringchain,op_hash)inreturn_unit)else((* The VDF setup saved in the state is different from the one computed
* from the on-chain seed. In practice this would indicate a bug, since
* it would either mean that the cycle has changed and we have not
* detected it or that the VDF setup changed mid-cycle. *)state.computation_status<-Invalid;let*!()=emit_with_level"Error injecting VDF: setup has been updated"levelinreturn_unit)|Nonce_revelation_stage->state.computation_status<-Not_started;let*!()=emit_with_level"Not injecting VDF: new cycle started"levelinreturn_unit|Computation_finished->state.computation_status<-Injected;let*!()=emit_with_level"Not injecting VDF: already injected"levelinreturn_unit(* Launch the heavy VDF computation as a separate process. This is done in order
* to not block the main process, allowing it to continue monitoring blocks and
* to cancel or restart the VDF computation if needed. *)letfork_vdf_computationstate((discriminant,challenge)assetup)level=letopenLwt_syntaxinletch_in,forked_out=Lwt_io.pipe()inmatchLwt_unix.fork()with|0->((* In the forked process, try to compute the VDF solution, write it
* to [forked_out], then exit. *)let*()=Lwt_io.closech_ininletsolution=Environment.Vdf.provediscriminantchallengestate.constants.parametric.vdf_difficultyinmatchData_encoding.Binary.to_bytesSeed.vdf_solution_encodingsolutionwith|Okencoded->let*()=Lwt_io.write_valueforked_outencodedinexit0|Error_->let*()=Events.(emitvdf_info)"Error encoding VDF solution"inexit1)|pid->(* In the main process, change the computation status to [Started],
record the forked process data, and continue. *)let*()=Lwt_io.closeforked_outinstate.computation_status<-Started(setup,{pid;ch_in});let*()=emit_with_level(Printf.sprintf"Started to compute VDF, pid: %d"pid)levelinreturn_unit(* Check whether the VDF computation process has exited and read the result.
* Update the computation status accordingly. *)letget_vdf_solution_if_readycctxtstateprocsetupchain_idhash(level_info:Level.t)=letopenLwt_result_syntaxinletlevel=level_info.levelinlet*!status=Lwt_unix.waitpid[WNOHANG]proc.pidinmatchstatuswith|0,_->(* If the process is still running, continue *)let*!()=emit_with_level"Skipping, VDF computation launched"levelinreturn_unit|_,WEXITED0->((* If the process has exited normally, read the solution, update
* the status to [Finished], and attempt to inject the VDF
* revelation. *)let*!encoded_solution=Lwt_io.read_valueproc.ch_ininmatchData_encoding.Binary.of_bytesSeed.vdf_solution_encodingencoded_solutionwith|Oksolution->let*!()=Lwt_io.closeproc.ch_ininstate.computation_status<-Finished(setup,solution);let*!()=emit_with_level"Finished VDF computation"levelininject_vdf_revelationcctxtstatesetupsolutionchain_idhashlevel_info|Error_->let*!()=Events.(emitvdf_info)"Error decoding VDF solution"instate.computation_status<-Not_started;return_unit)|_,WEXITED_|_,WSIGNALED_|_,WSTOPPED_->(* If process has exited abnormally, reset the computation status to
* [Not_started] and continue *)state.computation_status<-Not_started;let*!()=Events.(emitvdf_info)"VDF computation process exited abnormally"inreturn_unitletkill_forked_process{pid;_}=letopenLwt_syntaxinlet*()=matchUnix.killpidSys.sigtermwith|()->Events.(emitvdf_info)(Printf.sprintf"Sent SIGTERM to VDF computation process (pid %d)"pid)|exceptionUnix.Unix_error(err,_,_)->letmsg=Printf.sprintf"%s (pid %d)"(Unix.error_messageerr)pidinEvents.(emitvdf_daemon_cannot_kill_computation)msginlet*pid,status=Lwt_unix.waitpid[]pidinletstatus=matchstatuswith|WEXITEDn->Printf.sprintf"WEXITED %d"n|WSIGNALEDn->Printf.sprintf"WSIGNALED %d"n|WSTOPPEDn->Printf.sprintf"WSTOPPED %d"ninEvents.(emitvdf_info)(Printf.sprintf"Exit status for child VDF computation process %d: %s"pidstatus)(* Kill the VDF computation process if one was launched. *)letmaybe_kill_running_vdf_computationstate=letopenLwt_syntaxinmatchstate.computation_statuswith|Started(_,proc)->let*()=kill_forked_processprocinreturn_unit|_->return_unit(* Checks if the cycle of the last processed block is different from the cycle
* of the block at [level_info]. *)letcheck_new_cyclestate(level_info:Level.t)=letopenLwt_result_syntaxinletcurrent_cycle=level_info.cycleinmatchstate.cyclewith|None->(* First processed block, initialise [state.cycle] *)state.cycle<-Somecurrent_cycle;return_unit|Somecycle->ifCycle.(cycle<current_cycle)then((* The cycle of this block is different from the cycle of the last
* processed block. Emit an event if the VDF for the previous cycle
* has not been injected, kill any running VDF computation, and
* reset the computation status. *)let*()=matchstate.computation_statuswith|Injected->return_unit|Started((_:vdf_setup),proc)->let*!()=kill_forked_processprocinemit_revelation_not_injectedcycle|Not_started|Finished_|Invalid->emit_revelation_not_injectedcycleinstate.cycle<-Somecurrent_cycle;state.computation_status<-Not_started;return_unit)elsereturn_unit(* The daemon's main job is to launch the VDF computation as soon as it
* can (i.e., when the nonce revelation stage ends) and to inject
* the VDF solution as soon as it finishes computing it.
* Additionally, it must cancel a running VDF computation if its result
* is no longer required and restart a computation if it failed.
* The daemon processes the stream of blocks and monitors both
* the level of the head within a cycle and the [Seed_computation] RPC.
* The core of this function is a pattern match on the product of
* [seed_computation] (the on-chain status of the seed computation)
* and [state.computation_status] (the internal status of the daemon).
*
* [seed_computation] is reset at the beginning of a cycle to
* [Nonce_revelation_stage], mirroring the on-chain change of the computation
* status. No action is taken while in this state.
* After [nonce_revelation_threshold] blocks, the status becomes
* [Vdf_revelation_stage]. A call to the RPC confirms this and provides the seed
* required to launch the VDF computation.
* If a VDF revelation operation is injected before the end of the cycle,
* the status is updated to [Computation_finished]. If a VDF computation is
* running at that point (i.e., another daemon injected first),
* it is canceled. *)letprocess_new_block(cctxt:#Protocol_client_context.full)state{hash;chain_id;protocol;next_protocol;level;_}=letopenLwt_result_syntaxinifProtocol_hash.(protocol<>next_protocol)then(* If the protocol has changed, emit an event on every new block and take
* no further action. It is expected that the daemon corresponding to
* the new protocol is used instead. *)let*!()=Delegate_events.Denunciator.(emitprotocol_change_detected)()inreturn_unitelselet*level_info=get_level_infocctxtlevelin(* If head is in a new cycle record it in [state.cycle] and reset
* [state.computation_status] to [Not_started]. *)let*()=check_new_cyclestatelevel_infoin(* If the chain is in the nonce revelation stage, there is nothing to do. *)let*out=is_in_nonce_revelation_stagestate.constantslevel_infoinifoutthenlet*!()=emit_with_level"Skipping, still in nonce revelation stage"levelinreturn_unitelse(* Enter main loop if we are not in the nonce revelation stage and
* the expected protocol has been activated. *)matchstate.computation_statuswith|Not_started->(let*seed_computation=get_seed_computationcctxtchain_idhashinmatchseed_computationwith|Vdf_revelation_stage{seed_discriminant;seed_challenge}->(* The chain is in the VDF revelation stage and the computation
* has not been started, so it is started here, in a separate
* process. The computation status is updated to [Started]. *)letsetup=Seed.generate_vdf_setup~seed_discriminant~seed_challengeinlet*!()=fork_vdf_computationstatesetuplevelinreturn_unit|Computation_finished->let*!()=emit_with_level"Skipping, VDF solution has already been injected"levelinreturn_unit|Nonce_revelation_stage->(* At this point the chain cannot be in the nonce revelation
* stage. This is checked in [is_in_nonce_revelation_stage]. *)assertfalse)|Started(setup,proc)->(let*seed_computation=get_seed_computationcctxtchain_idhashinmatchseed_computationwith|Vdf_revelation_stage_->(* The chain is in the VDF computation stage and we have
* previously started the computation. Check whether it is
* finished and, if so, update the computation status to
* [Finished] and immediately inject the solution. *)let*()=get_vdf_solution_if_readycctxtstateprocsetupchain_idhashlevel_infoinreturn_unit|Computation_finished->(* The chain is no longer in the VDF revelation stage because
* the solution has already been injected: abort the running
* computation. *)let*!()=kill_forked_processprocinlet*!()=emit_with_level"VDF solution already injected, aborting VDF computation"levelinstate.computation_status<-Injected;return_unit|Nonce_revelation_stage->(* At this point the chain cannot be in the nonce revelation
* stage. This is checked in [is_in_nonce_revelation_stage]. *)assertfalse)|Finished(setup,solution)->(* VDF solution computed, but not injected. We are only in this case
* if the first attempt to inject, right after getting the solution,
* was unsuccessful. While the chain is in the VDF revelation stage,
* and the solution has not been injected (computation status is
* [Finished]), we try to inject. If successful, the computation
* status is updated to [Injected]. *)inject_vdf_revelationcctxtstatesetupsolutionchain_idhashlevel_info|Injected->let*!()=emit_with_level"Skipping, VDF solution already injected"levelinreturn_unit|Invalid->let*!()=emit_with_level"Skipping, failed to compute VDF"levelinreturn_unitletstart_vdf_worker(cctxt:Protocol_client_context.full)~cancelerconstantschain=letopenLwt_result_syntaxinlet*block_stream,stream_stopper=init_block_stream_with_stoppercctxtchaininletstate={cctxt;constants;block_stream;stream_stopper=Somestream_stopper;cycle=None;computation_status=Not_started;}inLwt_canceler.on_cancelcanceler(fun()->let*!()=maybe_kill_running_vdf_computationstateinstop_block_streamstate;Lwt.return_unit);letrecworker_loop()=let*!b=Lwt.choose[(let*!_=Lwt_exit.clean_up_startsinLwt.return`Termination);(let*!e=Lwt_stream.getstate.block_streaminLwt.return(`Blocke));]inmatchbwith|`Termination->return_unit|`Block(None|Some(Error_))->(* Exit when the node is unavailable *)stop_block_streamstate;let*!()=Events.(emitvdf_daemon_connection_lost)nameintzfailBaking_errors.Node_connection_lost|`Block(Some(Okbi))->let*!()=log_errors_and_continue~name@@process_new_blockcctxtstatebiinworker_loop()inworker_loop()