diff --git a/src/proto_016_PtMumbai/lib_delegate/baking_events.ml b/src/proto_016_PtMumbai/lib_delegate/baking_events.ml index daaac0e82776a087050d4baa27831f8362cd56b3..1e25f860b95c11e9478a75386dde44e396f928ab 100644 --- a/src/proto_016_PtMumbai/lib_delegate/baking_events.ml +++ b/src/proto_016_PtMumbai/lib_delegate/baking_events.ml @@ -787,7 +787,7 @@ module VDF = struct ~name:"vdf_revelation_injected" ~level:Notice ~msg: - "injected VDF revelation for cycle {cycle} (chain {chain} with \ + "Injected VDF revelation for cycle {cycle} (chain {chain} with \ operation {ophash})" ~pp1:pp_int32 ("cycle", Data_encoding.int32) @@ -796,38 +796,42 @@ module VDF = struct ~pp3:Operation_hash.pp ("ophash", Operation_hash.encoding) - let vdf_daemon_start = - declare_1 - ~section - ~level:Info - ~name:"vdf_daemon_start" - ~msg:"starting {worker} VDF daemon" - ("worker", Data_encoding.string) - let vdf_daemon_error = declare_2 ~section - ~level:Error ~name:"vdf_daemon_error" + ~level:Error ~msg:"{worker}: error while running VDF daemon: {errors}" - ~pp2:pp_print_top_error_of_trace + ~pp1:Format.pp_print_string ("worker", Data_encoding.string) + ~pp2:pp_print_top_error_of_trace ("errors", Error_monad.(TzTrace.encoding error_encoding)) let vdf_daemon_connection_lost = declare_1 ~section - ~level:Error ~name:"vdf_daemon_connection_lost" - ~msg:"connection to node lost, VDF daemon {worker} exiting" + ~level:Error + ~msg:"Connection to node lost, VDF daemon {worker} exiting" + ~pp1:Format.pp_print_string ("worker", Data_encoding.string) + let vdf_daemon_cannot_kill_computation = + declare_1 + ~section + ~name:"vdf_daemon_cannot_kill_computation" + ~level:Error + ~msg:"Error when killining running computation: {error}" + ~pp1:Format.pp_print_string + ("error", Data_encoding.string) + let vdf_info = declare_1 ~section ~name:"vdf_internal" ~level:Notice ~msg:"{msg}" + ~pp1:Format.pp_print_string ("msg", Data_encoding.string) end diff --git a/src/proto_016_PtMumbai/lib_delegate/baking_vdf.ml b/src/proto_016_PtMumbai/lib_delegate/baking_vdf.ml index c0e97b55ea74b76b00a2368a7f96d14ab4dc9880..504a3e58172660500c213a2b6ac19e3f31b10264 100644 --- a/src/proto_016_PtMumbai/lib_delegate/baking_vdf.ml +++ b/src/proto_016_PtMumbai/lib_delegate/baking_vdf.ml @@ -27,16 +27,17 @@ open Protocol open Alpha_context open Client_baking_blocks module Events = Baking_events.VDF -module D_Events = Delegate_events.Denunciator type vdf_solution = Seed_repr.vdf_solution type vdf_setup = Seed_repr.vdf_setup +type forked_process = {pid : int; ch_in : Lwt_io.input_channel} + type status = | Not_started - | Started - | Finished of vdf_solution + | Started of vdf_setup * forked_process + | Finished of vdf_setup * vdf_solution | Injected | Invalid @@ -47,7 +48,6 @@ type 'a state = { mutable stream_stopper : Tezos_rpc.Context.stopper option; mutable cycle : Cycle.t option; mutable computation_status : status; - mutable vdf_setup : vdf_setup option; } let init_block_stream_with_stopper cctxt chain = @@ -64,24 +64,18 @@ let stop_block_stream state = state.stream_stopper <- None) state.stream_stopper -let restart_block_stream cctxt chain state = +let emit_with_level msg level = + let level_i32 = Raw_level.to_int32 level in + Events.(emit vdf_info) (Printf.sprintf "%s (level %ld)" msg level_i32) + +let emit_revelation_not_injected cycle = let open Lwt_result_syntax in - stop_block_stream state ; - let retries_on_failure = 10 in - let rec try_start_block_stream retries_on_failure = - let*! p = init_block_stream_with_stopper cctxt chain in - match p with - | Ok (block_stream, stream_stopper) -> - state.block_stream <- block_stream ; - state.stream_stopper <- Some stream_stopper ; - return_unit - | Error e -> - if retries_on_failure > 0 then - let*! () = Lwt_unix.sleep 10. in - try_start_block_stream (retries_on_failure - 1) - else fail e + let*! () = + Events.(emit vdf_info) + (Printf.sprintf + "VDF revelation was NOT injected for cycle %ld" + (Cycle.to_int32 cycle)) in - let* () = try_start_block_stream retries_on_failure in return_unit let log_errors_and_continue ~name p = @@ -108,208 +102,336 @@ let get_level_info cctxt level = in return level_info -let is_in_nonce_revelation_period state (level_info : Level.t) = +let is_in_nonce_revelation_stage constants (level : Level.t) = let open Lwt_result_syntax in - let {Constants.parametric = {nonce_revelation_threshold; _}; _} = - state.constants + let {Constants.parametric = {nonce_revelation_threshold; _}; _} = constants in + return + (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 *) +let eq_vdf_setup vdf_setup seed_discriminant seed_challenge = + let open Environment.Vdf in + let saved_discriminant, saved_challenge = vdf_setup in + let discriminant, challenge = + Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge in - let position_in_cycle = level_info.cycle_position in - return (Int32.compare position_in_cycle nonce_revelation_threshold < 0) + Bytes.equal + (discriminant_to_bytes discriminant) + (discriminant_to_bytes saved_discriminant) + && Bytes.equal + (challenge_to_bytes challenge) + (challenge_to_bytes saved_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. *) +let inject_vdf_revelation cctxt state setup solution chain_id hash + (level_info : Level.t) = + let open Lwt_result_syntax in + let chain = `Hash chain_id in + let block = `Hash (hash, 0) in + let level = level_info.level in + let* seed_computation = get_seed_computation cctxt chain_id hash in + match seed_computation with + | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> + if eq_vdf_setup setup seed_discriminant seed_challenge then ( + let* op_bytes = + Plugin.RPC.Forge.vdf_revelation + cctxt + (chain, block) + ~branch:hash + ~solution + () + in + let op_bytes = Tezos_crypto.Signature.V_latest.(concat op_bytes zero) in + let* op_hash = + Shell_services.Injection.operation cctxt ~chain op_bytes + in + (* If injection is successful, update the status to [Injected]. *) + state.computation_status <- Injected ; + let*! () = + Events.(emit vdf_revelation_injected) + ( Cycle.to_int32 level_info.cycle, + Chain_services.to_string chain, + op_hash ) + in + return_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" level + in + return_unit) + | Nonce_revelation_stage -> + state.computation_status <- Not_started ; + let*! () = emit_with_level "Not injecting VDF: new cycle started" level in + return_unit + | Computation_finished -> + state.computation_status <- Injected ; + let*! () = emit_with_level "Not injecting VDF: already injected" level in + return_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. *) +let fork_vdf_computation state ((discriminant, challenge) as setup) level = + let open Lwt_syntax in + let ch_in, forked_out = Lwt_io.pipe () in + match Lwt_unix.fork () with + | 0 -> ( + (* In the forked process, try to compute the VDF solution, write it + * to [forked_out], then exit. *) + let* () = Lwt_io.close ch_in in + let solution = + Environment.Vdf.prove + discriminant + challenge + state.constants.parametric.vdf_difficulty + in + match + Data_encoding.Binary.to_bytes Seed.vdf_solution_encoding solution + with + | Ok encoded -> + let* () = Lwt_io.write_value forked_out encoded in + exit 0 + | Error _ -> + let* () = Events.(emit vdf_info) "Error encoding VDF solution" in + exit 1) + | pid -> + (* In the main process, change the computation status to [Started], + record the forked process data, and continue. *) + let* () = Lwt_io.close forked_out in + state.computation_status <- Started (setup, {pid; ch_in}) ; + let* () = + emit_with_level + (Printf.sprintf "Started to compute VDF, pid: %d" pid) + level + in + return_unit + +(* Check whether the VDF computation process has exited and read the result. + * Update the computation status accordingly. *) +let get_vdf_solution_if_ready cctxt state proc setup chain_id hash + (level_info : Level.t) = + let open Lwt_result_syntax in + let level = level_info.level in + let*! status = Lwt_unix.waitpid [Lwt_unix.WNOHANG] proc.pid in + match status with + | 0, _ -> + (* If the process is still running, continue *) + let*! () = emit_with_level "Skipping, VDF computation launched" level in + return_unit + | _, Lwt_unix.WEXITED 0 -> ( + (* 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_value proc.ch_in in + match + Data_encoding.Binary.of_bytes + Seed.vdf_solution_encoding + encoded_solution + with + | Ok solution -> + let*! () = Lwt_io.close proc.ch_in in + state.computation_status <- Finished (setup, solution) ; + let*! () = emit_with_level "Finished VDF computation" level in + inject_vdf_revelation + cctxt + state + setup + solution + chain_id + hash + level_info + | Error _ -> + let*! () = Events.(emit vdf_info) "Error decoding VDF solution" in + state.computation_status <- Not_started ; + return_unit) + | _, Lwt_unix.WEXITED _ | _, Lwt_unix.WSIGNALED _ | _, Lwt_unix.WSTOPPED _ -> + (* If process has exited abnormally, reset the computation status to + * [Not_started] and continue *) + state.computation_status <- Not_started ; + let*! () = + Events.(emit vdf_info) "VDF computation process exited abnormally" + in + return_unit + +let kill_running_vdf_computation {pid; _} = + let open Lwt_syntax in + let* () = + match Unix.kill pid Sys.sigterm with + | () -> Events.(emit vdf_info) "VDF computation was aborted" + | exception Unix.Unix_error (err, _, _) -> + let msg = Printf.sprintf "%s (pid %d)" (Unix.error_message err) pid in + Events.(emit vdf_daemon_cannot_kill_computation) msg + in + return_unit + +(* Checks if the cycle of the last processed block is different from the cycle + * of the block at [level_info]. *) let check_new_cycle state (level_info : Level.t) = let open Lwt_result_syntax in let current_cycle = level_info.cycle in match state.cycle with | None -> + (* First processed block, initialise [state.cycle] *) state.cycle <- Some current_cycle ; return_unit | Some cycle -> - if Cycle.(succ cycle <= current_cycle) then ( + if Cycle.(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 and reset the computation status. *) + * has not been injected, kill any running VDF computation, and + * reset the computation status. *) let* () = match state.computation_status with | Injected -> return_unit - | _ -> - let cycle_str = Int32.to_string (Cycle.to_int32 cycle) in - let*! () = - Events.(emit vdf_info) - ("VDF revelation was NOT injected for cycle " ^ cycle_str) - in - return_unit + | Started ((_ : vdf_setup), proc) -> + let*! () = kill_running_vdf_computation proc in + emit_revelation_not_injected cycle + | Not_started | Finished _ | Invalid -> + emit_revelation_not_injected cycle in state.cycle <- Some current_cycle ; state.computation_status <- Not_started ; return_unit) else return_unit -let inject_vdf_revelation cctxt hash chain_id solution = - let open Lwt_result_syntax in - let chain = `Hash chain_id in - let block = `Hash (hash, 0) in - let* bytes = - Plugin.RPC.Forge.vdf_revelation - cctxt - (chain, block) - ~branch:hash - ~solution - () - in - let bytes = Signature.concat bytes Signature.zero in - Shell_services.Injection.operation cctxt ~chain bytes - -(* Checks if the VDF setup saved in the state is equal to the one computed - from a seed *) -let eq_vdf_setup state seed_discriminant seed_challenge = - let open Environment.Vdf in - match state.vdf_setup with - | None -> assert false - | Some (saved_discriminant, saved_challenge) -> - let discriminant, challenge = - Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge - in - Bytes.equal - (discriminant_to_bytes discriminant) - (discriminant_to_bytes saved_discriminant) - && Bytes.equal - (challenge_to_bytes challenge) - (challenge_to_bytes saved_challenge) - +(* 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. *) let process_new_block (cctxt : #Protocol_client_context.full) state {hash; chain_id; protocol; next_protocol; level; _} = let open Lwt_result_syntax in let* level_info = get_level_info cctxt level in - let level_str = Int32.to_string (Raw_level.to_int32 level) in + (* If head is in a new cycle record it in [state.cycle] and reset + * [state.computation_status] to [Not_started]. *) let* () = check_new_cycle state level_info in if Protocol_hash.(protocol <> next_protocol) then - let*! () = D_Events.(emit protocol_change_detected) () in + (* 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.(emit protocol_change_detected) () in return_unit else - let* out = is_in_nonce_revelation_period state level_info in + (* If the chain is in the nonce revelation stage, there is nothing to do. *) + let* out = is_in_nonce_revelation_stage state.constants level_info in if out then let*! () = - Events.(emit vdf_info) - ("Skipping, still in nonce revelation period (level " ^ level_str - ^ ")") + emit_with_level "Skipping, still in nonce revelation stage" level in return_unit - (* enter main loop if we are not in the nonce revelation period and - the expected protocol has been activated *) else + (* Enter main loop if we are not in the nonce revelation stage and + * the expected protocol has been activated. *) match state.computation_status with - | Started -> - let*! () = - Events.(emit vdf_info) - ("Skipping, already started VDF (level " ^ level_str ^ ")") - in - return_unit | Not_started -> ( - let chain = `Hash chain_id in let* seed_computation = get_seed_computation cctxt chain_id hash in match seed_computation with | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> - state.computation_status <- Started ; - let*! () = - Events.(emit vdf_info) - ("Started to compute VDF (level " ^ level_str ^ ")") - in - let vdf_setup = + (* 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]. *) + let setup = Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge in - state.vdf_setup <- Some vdf_setup ; - stop_block_stream state ; - let* () = - Lwt.catch - (fun () -> - let discriminant, challenge = vdf_setup in - (* `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. *) - let solution = - Environment.Vdf.prove - discriminant - challenge - state.constants.parametric.vdf_difficulty - in - state.computation_status <- Finished solution ; - let*! () = Events.(emit vdf_info) "VDF solution computed" in - return_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.(emit vdf_info) - ("Failed to compute VDF solution (level " ^ level_str - ^ ")") - in - return_unit) + let*! () = fork_vdf_computation state setup level in + return_unit + | Computation_finished -> + let*! () = + emit_with_level + "Skipping, VDF solution has already been injected" + level in - restart_block_stream cctxt chain state - | Nonce_revelation_stage | Computation_finished -> - (* Daemon started too early or too late in a cycle, skipping. *) - return_unit) - | Finished solution -> ( - let*! () = - Events.(emit vdf_info) ("Finished VDF (level " ^ level_str ^ ")") - in - let chain = `Hash chain_id in + 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]. *) + assert false) + | Started (setup, proc) -> ( let* seed_computation = get_seed_computation cctxt chain_id hash in match seed_computation with - | 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. *) - if eq_vdf_setup state seed_discriminant seed_challenge then ( - let* op_hash = - inject_vdf_revelation cctxt hash chain_id solution - in - state.computation_status <- Injected ; - let*! () = - Events.(emit vdf_revelation_injected) - ( Cycle.to_int32 level_info.cycle, - Chain_services.to_string chain, - op_hash ) - in - return_unit) - else ( - state.computation_status <- Invalid ; - let*! () = - Events.(emit vdf_info) - ("Error injecting VDF: setup has been updated (level " - ^ level_str ^ ")") - in - return_unit) - | Nonce_revelation_stage -> - state.computation_status <- Not_started ; - let*! () = - Events.(emit vdf_info) - ("Error injecting VDF: new cycle started (level " ^ level_str - ^ ")") + | 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_ready + cctxt + state + proc + setup + chain_id + hash + level_info in return_unit | Computation_finished -> - state.computation_status <- Injected ; + (* The chain is no longer in the VDF revelation stage because + * the solution has already been injected: abort the running + * computation. *) + let*! () = kill_running_vdf_computation proc in let*! () = - Events.(emit vdf_info) - ("Error injecting VDF: already injected (level " ^ level_str - ^ ")") + emit_with_level + "VDF solution already injected, aborting VDF computation" + level in - return_unit) + state.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]. *) + assert false) + | 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_revelation + cctxt + state + setup + solution + chain_id + hash + level_info | Injected -> let*! () = - Events.(emit vdf_info) - ("Skipping, already injected VDF (level " ^ level_str ^ ")") + emit_with_level "Skipping, VDF solution already injected" level in return_unit | Invalid -> - let*! () = - Events.(emit vdf_info) - ("Skipping, failed to compute VDF (level " ^ level_str ^ ")") - in + let*! () = emit_with_level "Skipping, failed to compute VDF" level in return_unit let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants @@ -326,7 +448,6 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants stream_stopper = Some stream_stopper; cycle = None; computation_status = Not_started; - vdf_setup = None; } in Lwt_canceler.on_cancel canceler (fun () -> @@ -343,7 +464,7 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants match b with | `Termination -> return_unit | `Block (None | Some (Error _)) -> - (* exit when the node is unavailable *) + (* Exit when the node is unavailable *) stop_block_stream state ; let*! () = Events.(emit vdf_daemon_connection_lost) name in tzfail Baking_errors.Node_connection_lost @@ -353,5 +474,4 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants in worker_loop () in - let*! () = Events.(emit vdf_daemon_start) name in worker_loop () diff --git a/src/proto_016_PtMumbai/lib_delegate/vdf_helpers.ml b/src/proto_016_PtMumbai/lib_delegate/vdf_helpers.ml new file mode 100644 index 0000000000000000000000000000000000000000..b3fe451bc1d59ff5af42115f0fe43efa7b7db03f --- /dev/null +++ b/src/proto_016_PtMumbai/lib_delegate/vdf_helpers.ml @@ -0,0 +1,31 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol.Alpha_context + +let is_in_nonce_revelation_stage ~nonce_revelation_threshold ~(level : Level.t) + = + let cycle_position = level.cycle_position in + Int32.compare cycle_position nonce_revelation_threshold < 0 diff --git a/src/proto_016_PtMumbai/lib_delegate/vdf_helpers.mli b/src/proto_016_PtMumbai/lib_delegate/vdf_helpers.mli new file mode 100644 index 0000000000000000000000000000000000000000..1000c59bece0520432e5cd0a2678d6722e924320 --- /dev/null +++ b/src/proto_016_PtMumbai/lib_delegate/vdf_helpers.mli @@ -0,0 +1,35 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol.Alpha_context + +(** [is_in_nonce_revelation_stage ~nonce_revelation_threshold ~level] checks + whether [level] is part of the nonce revelation stage of its cycle, which + is defined as the first [nonce_revelation_threshold] blocks of every cycle. + It is used to avoid calling the [Seed_computation] RPC on blocks which are + part of the nonce revelation stage. + *) +val is_in_nonce_revelation_stage : + nonce_revelation_threshold:int32 -> level:Level.t -> bool diff --git a/src/proto_017_PtNairob/lib_delegate/baking_events.ml b/src/proto_017_PtNairob/lib_delegate/baking_events.ml index 5038fec32add0c1e3b10eae829d05f119175c95b..82b9a38834ca2ebd3ce5792765b94e05e33ee8bb 100644 --- a/src/proto_017_PtNairob/lib_delegate/baking_events.ml +++ b/src/proto_017_PtNairob/lib_delegate/baking_events.ml @@ -821,7 +821,7 @@ module VDF = struct ~name:"vdf_revelation_injected" ~level:Notice ~msg: - "injected VDF revelation for cycle {cycle} (chain {chain} with \ + "Injected VDF revelation for cycle {cycle} (chain {chain} with \ operation {ophash})" ~pp1:pp_int32 ("cycle", Data_encoding.int32) @@ -830,38 +830,42 @@ module VDF = struct ~pp3:Operation_hash.pp ("ophash", Operation_hash.encoding) - let vdf_daemon_start = - declare_1 - ~section - ~level:Info - ~name:"vdf_daemon_start" - ~msg:"starting {worker} VDF daemon" - ("worker", Data_encoding.string) - let vdf_daemon_error = declare_2 ~section - ~level:Error ~name:"vdf_daemon_error" + ~level:Error ~msg:"{worker}: error while running VDF daemon: {errors}" - ~pp2:pp_print_top_error_of_trace + ~pp1:Format.pp_print_string ("worker", Data_encoding.string) + ~pp2:pp_print_top_error_of_trace ("errors", Error_monad.(TzTrace.encoding error_encoding)) let vdf_daemon_connection_lost = declare_1 ~section - ~level:Error ~name:"vdf_daemon_connection_lost" - ~msg:"connection to node lost, VDF daemon {worker} exiting" + ~level:Error + ~msg:"Connection to node lost, VDF daemon {worker} exiting" + ~pp1:Format.pp_print_string ("worker", Data_encoding.string) + let vdf_daemon_cannot_kill_computation = + declare_1 + ~section + ~name:"vdf_daemon_cannot_kill_computation" + ~level:Error + ~msg:"Error when killining running computation: {error}" + ~pp1:Format.pp_print_string + ("error", Data_encoding.string) + let vdf_info = declare_1 ~section ~name:"vdf_internal" ~level:Notice ~msg:"{msg}" + ~pp1:Format.pp_print_string ("msg", Data_encoding.string) end diff --git a/src/proto_017_PtNairob/lib_delegate/baking_vdf.ml b/src/proto_017_PtNairob/lib_delegate/baking_vdf.ml index c0e97b55ea74b76b00a2368a7f96d14ab4dc9880..504a3e58172660500c213a2b6ac19e3f31b10264 100644 --- a/src/proto_017_PtNairob/lib_delegate/baking_vdf.ml +++ b/src/proto_017_PtNairob/lib_delegate/baking_vdf.ml @@ -27,16 +27,17 @@ open Protocol open Alpha_context open Client_baking_blocks module Events = Baking_events.VDF -module D_Events = Delegate_events.Denunciator type vdf_solution = Seed_repr.vdf_solution type vdf_setup = Seed_repr.vdf_setup +type forked_process = {pid : int; ch_in : Lwt_io.input_channel} + type status = | Not_started - | Started - | Finished of vdf_solution + | Started of vdf_setup * forked_process + | Finished of vdf_setup * vdf_solution | Injected | Invalid @@ -47,7 +48,6 @@ type 'a state = { mutable stream_stopper : Tezos_rpc.Context.stopper option; mutable cycle : Cycle.t option; mutable computation_status : status; - mutable vdf_setup : vdf_setup option; } let init_block_stream_with_stopper cctxt chain = @@ -64,24 +64,18 @@ let stop_block_stream state = state.stream_stopper <- None) state.stream_stopper -let restart_block_stream cctxt chain state = +let emit_with_level msg level = + let level_i32 = Raw_level.to_int32 level in + Events.(emit vdf_info) (Printf.sprintf "%s (level %ld)" msg level_i32) + +let emit_revelation_not_injected cycle = let open Lwt_result_syntax in - stop_block_stream state ; - let retries_on_failure = 10 in - let rec try_start_block_stream retries_on_failure = - let*! p = init_block_stream_with_stopper cctxt chain in - match p with - | Ok (block_stream, stream_stopper) -> - state.block_stream <- block_stream ; - state.stream_stopper <- Some stream_stopper ; - return_unit - | Error e -> - if retries_on_failure > 0 then - let*! () = Lwt_unix.sleep 10. in - try_start_block_stream (retries_on_failure - 1) - else fail e + let*! () = + Events.(emit vdf_info) + (Printf.sprintf + "VDF revelation was NOT injected for cycle %ld" + (Cycle.to_int32 cycle)) in - let* () = try_start_block_stream retries_on_failure in return_unit let log_errors_and_continue ~name p = @@ -108,208 +102,336 @@ let get_level_info cctxt level = in return level_info -let is_in_nonce_revelation_period state (level_info : Level.t) = +let is_in_nonce_revelation_stage constants (level : Level.t) = let open Lwt_result_syntax in - let {Constants.parametric = {nonce_revelation_threshold; _}; _} = - state.constants + let {Constants.parametric = {nonce_revelation_threshold; _}; _} = constants in + return + (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 *) +let eq_vdf_setup vdf_setup seed_discriminant seed_challenge = + let open Environment.Vdf in + let saved_discriminant, saved_challenge = vdf_setup in + let discriminant, challenge = + Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge in - let position_in_cycle = level_info.cycle_position in - return (Int32.compare position_in_cycle nonce_revelation_threshold < 0) + Bytes.equal + (discriminant_to_bytes discriminant) + (discriminant_to_bytes saved_discriminant) + && Bytes.equal + (challenge_to_bytes challenge) + (challenge_to_bytes saved_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. *) +let inject_vdf_revelation cctxt state setup solution chain_id hash + (level_info : Level.t) = + let open Lwt_result_syntax in + let chain = `Hash chain_id in + let block = `Hash (hash, 0) in + let level = level_info.level in + let* seed_computation = get_seed_computation cctxt chain_id hash in + match seed_computation with + | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> + if eq_vdf_setup setup seed_discriminant seed_challenge then ( + let* op_bytes = + Plugin.RPC.Forge.vdf_revelation + cctxt + (chain, block) + ~branch:hash + ~solution + () + in + let op_bytes = Tezos_crypto.Signature.V_latest.(concat op_bytes zero) in + let* op_hash = + Shell_services.Injection.operation cctxt ~chain op_bytes + in + (* If injection is successful, update the status to [Injected]. *) + state.computation_status <- Injected ; + let*! () = + Events.(emit vdf_revelation_injected) + ( Cycle.to_int32 level_info.cycle, + Chain_services.to_string chain, + op_hash ) + in + return_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" level + in + return_unit) + | Nonce_revelation_stage -> + state.computation_status <- Not_started ; + let*! () = emit_with_level "Not injecting VDF: new cycle started" level in + return_unit + | Computation_finished -> + state.computation_status <- Injected ; + let*! () = emit_with_level "Not injecting VDF: already injected" level in + return_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. *) +let fork_vdf_computation state ((discriminant, challenge) as setup) level = + let open Lwt_syntax in + let ch_in, forked_out = Lwt_io.pipe () in + match Lwt_unix.fork () with + | 0 -> ( + (* In the forked process, try to compute the VDF solution, write it + * to [forked_out], then exit. *) + let* () = Lwt_io.close ch_in in + let solution = + Environment.Vdf.prove + discriminant + challenge + state.constants.parametric.vdf_difficulty + in + match + Data_encoding.Binary.to_bytes Seed.vdf_solution_encoding solution + with + | Ok encoded -> + let* () = Lwt_io.write_value forked_out encoded in + exit 0 + | Error _ -> + let* () = Events.(emit vdf_info) "Error encoding VDF solution" in + exit 1) + | pid -> + (* In the main process, change the computation status to [Started], + record the forked process data, and continue. *) + let* () = Lwt_io.close forked_out in + state.computation_status <- Started (setup, {pid; ch_in}) ; + let* () = + emit_with_level + (Printf.sprintf "Started to compute VDF, pid: %d" pid) + level + in + return_unit + +(* Check whether the VDF computation process has exited and read the result. + * Update the computation status accordingly. *) +let get_vdf_solution_if_ready cctxt state proc setup chain_id hash + (level_info : Level.t) = + let open Lwt_result_syntax in + let level = level_info.level in + let*! status = Lwt_unix.waitpid [Lwt_unix.WNOHANG] proc.pid in + match status with + | 0, _ -> + (* If the process is still running, continue *) + let*! () = emit_with_level "Skipping, VDF computation launched" level in + return_unit + | _, Lwt_unix.WEXITED 0 -> ( + (* 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_value proc.ch_in in + match + Data_encoding.Binary.of_bytes + Seed.vdf_solution_encoding + encoded_solution + with + | Ok solution -> + let*! () = Lwt_io.close proc.ch_in in + state.computation_status <- Finished (setup, solution) ; + let*! () = emit_with_level "Finished VDF computation" level in + inject_vdf_revelation + cctxt + state + setup + solution + chain_id + hash + level_info + | Error _ -> + let*! () = Events.(emit vdf_info) "Error decoding VDF solution" in + state.computation_status <- Not_started ; + return_unit) + | _, Lwt_unix.WEXITED _ | _, Lwt_unix.WSIGNALED _ | _, Lwt_unix.WSTOPPED _ -> + (* If process has exited abnormally, reset the computation status to + * [Not_started] and continue *) + state.computation_status <- Not_started ; + let*! () = + Events.(emit vdf_info) "VDF computation process exited abnormally" + in + return_unit + +let kill_running_vdf_computation {pid; _} = + let open Lwt_syntax in + let* () = + match Unix.kill pid Sys.sigterm with + | () -> Events.(emit vdf_info) "VDF computation was aborted" + | exception Unix.Unix_error (err, _, _) -> + let msg = Printf.sprintf "%s (pid %d)" (Unix.error_message err) pid in + Events.(emit vdf_daemon_cannot_kill_computation) msg + in + return_unit + +(* Checks if the cycle of the last processed block is different from the cycle + * of the block at [level_info]. *) let check_new_cycle state (level_info : Level.t) = let open Lwt_result_syntax in let current_cycle = level_info.cycle in match state.cycle with | None -> + (* First processed block, initialise [state.cycle] *) state.cycle <- Some current_cycle ; return_unit | Some cycle -> - if Cycle.(succ cycle <= current_cycle) then ( + if Cycle.(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 and reset the computation status. *) + * has not been injected, kill any running VDF computation, and + * reset the computation status. *) let* () = match state.computation_status with | Injected -> return_unit - | _ -> - let cycle_str = Int32.to_string (Cycle.to_int32 cycle) in - let*! () = - Events.(emit vdf_info) - ("VDF revelation was NOT injected for cycle " ^ cycle_str) - in - return_unit + | Started ((_ : vdf_setup), proc) -> + let*! () = kill_running_vdf_computation proc in + emit_revelation_not_injected cycle + | Not_started | Finished _ | Invalid -> + emit_revelation_not_injected cycle in state.cycle <- Some current_cycle ; state.computation_status <- Not_started ; return_unit) else return_unit -let inject_vdf_revelation cctxt hash chain_id solution = - let open Lwt_result_syntax in - let chain = `Hash chain_id in - let block = `Hash (hash, 0) in - let* bytes = - Plugin.RPC.Forge.vdf_revelation - cctxt - (chain, block) - ~branch:hash - ~solution - () - in - let bytes = Signature.concat bytes Signature.zero in - Shell_services.Injection.operation cctxt ~chain bytes - -(* Checks if the VDF setup saved in the state is equal to the one computed - from a seed *) -let eq_vdf_setup state seed_discriminant seed_challenge = - let open Environment.Vdf in - match state.vdf_setup with - | None -> assert false - | Some (saved_discriminant, saved_challenge) -> - let discriminant, challenge = - Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge - in - Bytes.equal - (discriminant_to_bytes discriminant) - (discriminant_to_bytes saved_discriminant) - && Bytes.equal - (challenge_to_bytes challenge) - (challenge_to_bytes saved_challenge) - +(* 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. *) let process_new_block (cctxt : #Protocol_client_context.full) state {hash; chain_id; protocol; next_protocol; level; _} = let open Lwt_result_syntax in let* level_info = get_level_info cctxt level in - let level_str = Int32.to_string (Raw_level.to_int32 level) in + (* If head is in a new cycle record it in [state.cycle] and reset + * [state.computation_status] to [Not_started]. *) let* () = check_new_cycle state level_info in if Protocol_hash.(protocol <> next_protocol) then - let*! () = D_Events.(emit protocol_change_detected) () in + (* 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.(emit protocol_change_detected) () in return_unit else - let* out = is_in_nonce_revelation_period state level_info in + (* If the chain is in the nonce revelation stage, there is nothing to do. *) + let* out = is_in_nonce_revelation_stage state.constants level_info in if out then let*! () = - Events.(emit vdf_info) - ("Skipping, still in nonce revelation period (level " ^ level_str - ^ ")") + emit_with_level "Skipping, still in nonce revelation stage" level in return_unit - (* enter main loop if we are not in the nonce revelation period and - the expected protocol has been activated *) else + (* Enter main loop if we are not in the nonce revelation stage and + * the expected protocol has been activated. *) match state.computation_status with - | Started -> - let*! () = - Events.(emit vdf_info) - ("Skipping, already started VDF (level " ^ level_str ^ ")") - in - return_unit | Not_started -> ( - let chain = `Hash chain_id in let* seed_computation = get_seed_computation cctxt chain_id hash in match seed_computation with | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> - state.computation_status <- Started ; - let*! () = - Events.(emit vdf_info) - ("Started to compute VDF (level " ^ level_str ^ ")") - in - let vdf_setup = + (* 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]. *) + let setup = Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge in - state.vdf_setup <- Some vdf_setup ; - stop_block_stream state ; - let* () = - Lwt.catch - (fun () -> - let discriminant, challenge = vdf_setup in - (* `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. *) - let solution = - Environment.Vdf.prove - discriminant - challenge - state.constants.parametric.vdf_difficulty - in - state.computation_status <- Finished solution ; - let*! () = Events.(emit vdf_info) "VDF solution computed" in - return_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.(emit vdf_info) - ("Failed to compute VDF solution (level " ^ level_str - ^ ")") - in - return_unit) + let*! () = fork_vdf_computation state setup level in + return_unit + | Computation_finished -> + let*! () = + emit_with_level + "Skipping, VDF solution has already been injected" + level in - restart_block_stream cctxt chain state - | Nonce_revelation_stage | Computation_finished -> - (* Daemon started too early or too late in a cycle, skipping. *) - return_unit) - | Finished solution -> ( - let*! () = - Events.(emit vdf_info) ("Finished VDF (level " ^ level_str ^ ")") - in - let chain = `Hash chain_id in + 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]. *) + assert false) + | Started (setup, proc) -> ( let* seed_computation = get_seed_computation cctxt chain_id hash in match seed_computation with - | 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. *) - if eq_vdf_setup state seed_discriminant seed_challenge then ( - let* op_hash = - inject_vdf_revelation cctxt hash chain_id solution - in - state.computation_status <- Injected ; - let*! () = - Events.(emit vdf_revelation_injected) - ( Cycle.to_int32 level_info.cycle, - Chain_services.to_string chain, - op_hash ) - in - return_unit) - else ( - state.computation_status <- Invalid ; - let*! () = - Events.(emit vdf_info) - ("Error injecting VDF: setup has been updated (level " - ^ level_str ^ ")") - in - return_unit) - | Nonce_revelation_stage -> - state.computation_status <- Not_started ; - let*! () = - Events.(emit vdf_info) - ("Error injecting VDF: new cycle started (level " ^ level_str - ^ ")") + | 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_ready + cctxt + state + proc + setup + chain_id + hash + level_info in return_unit | Computation_finished -> - state.computation_status <- Injected ; + (* The chain is no longer in the VDF revelation stage because + * the solution has already been injected: abort the running + * computation. *) + let*! () = kill_running_vdf_computation proc in let*! () = - Events.(emit vdf_info) - ("Error injecting VDF: already injected (level " ^ level_str - ^ ")") + emit_with_level + "VDF solution already injected, aborting VDF computation" + level in - return_unit) + state.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]. *) + assert false) + | 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_revelation + cctxt + state + setup + solution + chain_id + hash + level_info | Injected -> let*! () = - Events.(emit vdf_info) - ("Skipping, already injected VDF (level " ^ level_str ^ ")") + emit_with_level "Skipping, VDF solution already injected" level in return_unit | Invalid -> - let*! () = - Events.(emit vdf_info) - ("Skipping, failed to compute VDF (level " ^ level_str ^ ")") - in + let*! () = emit_with_level "Skipping, failed to compute VDF" level in return_unit let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants @@ -326,7 +448,6 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants stream_stopper = Some stream_stopper; cycle = None; computation_status = Not_started; - vdf_setup = None; } in Lwt_canceler.on_cancel canceler (fun () -> @@ -343,7 +464,7 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants match b with | `Termination -> return_unit | `Block (None | Some (Error _)) -> - (* exit when the node is unavailable *) + (* Exit when the node is unavailable *) stop_block_stream state ; let*! () = Events.(emit vdf_daemon_connection_lost) name in tzfail Baking_errors.Node_connection_lost @@ -353,5 +474,4 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants in worker_loop () in - let*! () = Events.(emit vdf_daemon_start) name in worker_loop () diff --git a/src/proto_017_PtNairob/lib_delegate/vdf_helpers.ml b/src/proto_017_PtNairob/lib_delegate/vdf_helpers.ml new file mode 100644 index 0000000000000000000000000000000000000000..b3fe451bc1d59ff5af42115f0fe43efa7b7db03f --- /dev/null +++ b/src/proto_017_PtNairob/lib_delegate/vdf_helpers.ml @@ -0,0 +1,31 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol.Alpha_context + +let is_in_nonce_revelation_stage ~nonce_revelation_threshold ~(level : Level.t) + = + let cycle_position = level.cycle_position in + Int32.compare cycle_position nonce_revelation_threshold < 0 diff --git a/src/proto_017_PtNairob/lib_delegate/vdf_helpers.mli b/src/proto_017_PtNairob/lib_delegate/vdf_helpers.mli new file mode 100644 index 0000000000000000000000000000000000000000..1000c59bece0520432e5cd0a2678d6722e924320 --- /dev/null +++ b/src/proto_017_PtNairob/lib_delegate/vdf_helpers.mli @@ -0,0 +1,35 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol.Alpha_context + +(** [is_in_nonce_revelation_stage ~nonce_revelation_threshold ~level] checks + whether [level] is part of the nonce revelation stage of its cycle, which + is defined as the first [nonce_revelation_threshold] blocks of every cycle. + It is used to avoid calling the [Seed_computation] RPC on blocks which are + part of the nonce revelation stage. + *) +val is_in_nonce_revelation_stage : + nonce_revelation_threshold:int32 -> level:Level.t -> bool