123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359(*****************************************************************************)(* *)(* 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.VDFmoduleD_Events=Delegate_events.Denunciatortypevdf_solution=Seed_repr.vdf_solutiontypevdf_setup=Seed_repr.vdf_setuptypestatus=|Not_started|Started|Finishedofvdf_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;mutablevdf_setup:vdf_setupoption;}letinit_block_stream_with_stoppercctxtchain=Client_baking_blocks.monitor_applied_blocks~next_protocols:(Some[Protocol.hash])cctxt~chains:[chain]()letstop_block_streamstate=Option.iter(funstopper->stopper();state.stream_stopper<-None)state.stream_stopperletrestart_block_streamcctxtchainstate=letopenLwt_result_syntaxinstop_block_streamstate;letretries_on_failure=10inletrectry_start_block_streamretries_on_failure=let*!p=init_block_stream_with_stoppercctxtchaininmatchpwith|Ok(block_stream,stream_stopper)->state.block_stream<-block_stream;state.stream_stopper<-Somestream_stopper;return_unit|Errore->ifretries_on_failure>0thenlet*!()=Lwt_unix.sleep10.intry_start_block_stream(retries_on_failure-1)elsefaileinlet*()=try_start_block_streamretries_on_failureinreturn_unitletlog_errors_and_continue~namep=letopenLwt_syntaxinlet*p=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_periodstate(level_info:Level.t)=letopenLwt_result_syntaxinlet{Constants.parametric={nonce_revelation_threshold;_};_}=state.constantsinletposition_in_cycle=level_info.cycle_positioninreturn(Int32.compareposition_in_cyclenonce_revelation_threshold<0)letcheck_new_cyclestate(level_info:Level.t)=letopenLwt_result_syntaxinletcurrent_cycle=level_info.cycleinmatchstate.cyclewith|None->state.cycle<-Somecurrent_cycle;return_unit|Somecycle->ifCycle.(succcycle<=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 and reset the computation status. *)let*()=matchstate.computation_statuswith|Injected->return_unit|_->letcycle_str=Int32.to_string(Cycle.to_int32cycle)inlet*!()=Events.(emitvdf_info)("VDF revelation was NOT injected for cycle "^cycle_str)inreturn_unitinstate.cycle<-Somecurrent_cycle;state.computation_status<-Not_started;return_unit)elsereturn_unitletinject_vdf_revelationcctxthashchain_idsolution=letopenLwt_result_syntaxinletchain=`Hashchain_idinletblock=`Hash(hash,0)inlet*bytes=Plugin.RPC.Forge.vdf_revelationcctxt(chain,block)~branch:hash~solution()inletbytes=Tezos_crypto.Signature.V0.concatbytesTezos_crypto.Signature.V0.zeroinShell_services.Injection.operationcctxt~chainbytes(* Checks if the VDF setup saved in the state is equal to the one computed
from a seed *)leteq_vdf_setupstateseed_discriminantseed_challenge=letopenEnvironment.Vdfinmatchstate.vdf_setupwith|None->assertfalse|Some(saved_discriminant,saved_challenge)->letdiscriminant,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)letprocess_new_block(cctxt:#Protocol_client_context.full)state{hash;chain_id;protocol;next_protocol;level;_}=letopenLwt_result_syntaxinlet*level_info=get_level_infocctxtlevelinletlevel_str=Int32.to_string(Raw_level.to_int32level)inlet*()=check_new_cyclestatelevel_infoinifProtocol_hash.(protocol<>next_protocol)thenlet*!()=D_Events.(emitprotocol_change_detected)()inreturn_unitelselet*out=is_in_nonce_revelation_periodstatelevel_infoinifoutthenlet*!()=Events.(emitvdf_info)("Skipping, still in nonce revelation period (level "^level_str^")")inreturn_unit(* enter main loop if we are not in the nonce revelation period and
the expected protocol has been activated *)elsematchstate.computation_statuswith|Started->let*!()=Events.(emitvdf_info)("Skipping, already started VDF (level "^level_str^")")inreturn_unit|Not_started->(letchain=`Hashchain_idinlet*seed_computation=get_seed_computationcctxtchain_idhashinmatchseed_computationwith|Vdf_revelation_stage{seed_discriminant;seed_challenge}->state.computation_status<-Started;let*!()=Events.(emitvdf_info)("Started to compute VDF (level "^level_str^")")inletvdf_setup=Seed.generate_vdf_setup~seed_discriminant~seed_challengeinstate.vdf_setup<-Somevdf_setup;stop_block_streamstate;let*()=Lwt.catch(fun()->letdiscriminant,challenge=vdf_setupin(* `Vdf.prove` is a long computation. We reset the block
* stream in order to not process all the blocks added
* to the chain during this time and skip straight to
* the current head. *)letsolution=Environment.Vdf.provediscriminantchallengestate.constants.parametric.vdf_difficultyinstate.computation_status<-Finishedsolution;let*!()=Events.(emitvdf_info)"VDF solution computed"inreturn_unit)(fun_->(* VDF computation failed with an error thrown by the external
* library. We set the status back to Not_started in order to
* retry computing it if still possible. *)state.computation_status<-Not_started;let*!()=Events.(emitvdf_info)("Failed to compute VDF solution (level "^level_str^")")inreturn_unit)inrestart_block_streamcctxtchainstate|Nonce_revelation_stage|Computation_finished->(* Daemon started too early or too late in a cycle, skipping. *)return_unit)|Finishedsolution->(let*!()=Events.(emitvdf_info)("Finished VDF (level "^level_str^")")inletchain=`Hashchain_idinlet*seed_computation=get_seed_computationcctxtchain_idhashinmatchseed_computationwith|Vdf_revelation_stage{seed_discriminant;seed_challenge}->(* If a solution has been computed that is consistent with the VDF
* setup for the current cycle and we are still in the VDF
* revelation stage, inject the operation. *)ifeq_vdf_setupstateseed_discriminantseed_challengethen(let*op_hash=inject_vdf_revelationcctxthashchain_idsolutioninstate.computation_status<-Injected;let*!()=Events.(emitvdf_revelation_injected)(Cycle.to_int32level_info.cycle,Chain_services.to_stringchain,op_hash)inreturn_unit)else(state.computation_status<-Invalid;let*!()=Events.(emitvdf_info)("Error injecting VDF: setup has been updated (level "^level_str^")")inreturn_unit)|Nonce_revelation_stage->state.computation_status<-Not_started;let*!()=Events.(emitvdf_info)("Error injecting VDF: new cycle started (level "^level_str^")")inreturn_unit|Computation_finished->state.computation_status<-Injected;let*!()=Events.(emitvdf_info)("Error injecting VDF: already injected (level "^level_str^")")inreturn_unit)|Injected->let*!()=Events.(emitvdf_info)("Skipping, already injected VDF (level "^level_str^")")inreturn_unit|Invalid->let*!()=Events.(emitvdf_info)("Skipping, failed to compute VDF (level "^level_str^")")inreturn_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;vdf_setup=None;}inLwt_canceler.on_cancelcanceler(fun()->stop_block_streamstate;Lwt.return_unit);letrecworker_loop()=let*!b=Lwt.choose[(Lwt_exit.clean_up_starts>|=fun_->`Termination);(Lwt_stream.getstate.block_stream>|=fune->`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()inlet*!()=Events.(emitvdf_daemon_start)nameinworker_loop()