From 45730ed1007a7479fb17310c59fc8d2f9328b750 Mon Sep 17 00:00:00 2001 From: victor-dumitrescu Date: Mon, 9 Jan 2023 10:22:35 +0100 Subject: [PATCH 1/5] Proto/VDF-daemon: update VDF daemon events --- src/proto_alpha/lib_delegate/baking_events.ml | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_events.ml b/src/proto_alpha/lib_delegate/baking_events.ml index 91e55fb1bf5c..0dcb4a19be71 100644 --- a/src/proto_alpha/lib_delegate/baking_events.ml +++ b/src/proto_alpha/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 -- GitLab From 20de43681403bb2cac6386fa836497e7b8401309 Mon Sep 17 00:00:00 2001 From: victor-dumitrescu Date: Mon, 9 Jan 2023 10:35:25 +0100 Subject: [PATCH 2/5] Proto/VDF-daemon: fork VDF computation in separate process --- src/proto_alpha/lib_delegate/baking_vdf.ml | 439 +++++++++++++-------- tezt/tests/vdf_test.ml | 6 +- 2 files changed, 283 insertions(+), 162 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_vdf.ml b/src/proto_alpha/lib_delegate/baking_vdf.ml index c0e97b55ea74..4684c70015e9 100644 --- a/src/proto_alpha/lib_delegate/baking_vdf.ml +++ b/src/proto_alpha/lib_delegate/baking_vdf.ml @@ -27,15 +27,16 @@ 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 + | Started of forked_process | Finished of vdf_solution | Injected | Invalid @@ -64,24 +65,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,7 +103,10 @@ let get_level_info cctxt level = in return level_info -let is_in_nonce_revelation_period state (level_info : Level.t) = +(* Determines whether the current block is in the nonce revelation stage by + * comparing its position in the cycle with the nonce revelation threshold, not + * by querying the [Seed_computation] RPC. *) +let is_in_nonce_revelation_stage state (level_info : Level.t) = let open Lwt_result_syntax in let {Constants.parametric = {nonce_revelation_threshold; _}; _} = state.constants @@ -116,200 +114,320 @@ let is_in_nonce_revelation_period state (level_info : Level.t) = let position_in_cycle = level_info.cycle_position in return (Int32.compare position_in_cycle nonce_revelation_threshold < 0) +(* 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) + +(* 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 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 state 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 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 {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 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 solution ; + let*! () = emit_with_level "Finished VDF computation" level in + inject_vdf_revelation cctxt state 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 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 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 + (* 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 vdf_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 discriminant, challenge = vdf_setup in + let*! () = + fork_vdf_computation state discriminant challenge 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 + | Computation_finished -> + let*! () = + emit_with_level + "Skipping, VDF solution has already been injected" + level + 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 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 + 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 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 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 @@ -343,7 +461,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 +471,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/tezt/tests/vdf_test.ml b/tezt/tests/vdf_test.ml index e42078e7ea55..b48a26d0df64 100644 --- a/tezt/tests/vdf_test.ml +++ b/tezt/tests/vdf_test.ml @@ -244,6 +244,8 @@ let test_vdf : Protocol.t list -> unit = in let* vdf_baker = Vdf.init ~protocol node in init_vdf_event_listener vdf_baker injected ; + let* vdf_baker2 = Vdf.init ~protocol node in + init_vdf_event_listener vdf_baker2 injected ; (* Bake to the end of the cycle and check that the VDF was not injected. *) let* level = @@ -262,6 +264,8 @@ let test_vdf : Protocol.t list -> unit = injected in - Vdf.terminate vdf_baker + let* () = Vdf.terminate vdf_baker in + let* () = Vdf.terminate vdf_baker2 in + Lwt.return_unit let register ~protocols = test_vdf protocols -- GitLab From 31bc1302450ec231900e6d63c5bff5acb53a9f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Proust?= Date: Fri, 20 Jan 2023 11:03:18 +0100 Subject: [PATCH 3/5] Proto/VDF-daemon: inline setup inside status to avoid mutability --- src/proto_alpha/lib_delegate/baking_vdf.ml | 76 ++++++++++++---------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_vdf.ml b/src/proto_alpha/lib_delegate/baking_vdf.ml index 4684c70015e9..c1810513c250 100644 --- a/src/proto_alpha/lib_delegate/baking_vdf.ml +++ b/src/proto_alpha/lib_delegate/baking_vdf.ml @@ -36,8 +36,8 @@ type forked_process = {pid : int; ch_in : Lwt_io.input_channel} type status = | Not_started - | Started of forked_process - | Finished of vdf_solution + | Started of vdf_setup * forked_process + | Finished of vdf_setup * vdf_solution | Injected | Invalid @@ -48,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 = @@ -116,27 +115,25 @@ let is_in_nonce_revelation_stage state (level_info : Level.t) = (* 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 eq_vdf_setup vdf_setup 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) + let saved_discriminant, saved_challenge = vdf_setup in + 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) (* 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 solution chain_id hash +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 @@ -145,7 +142,7 @@ let inject_vdf_revelation cctxt state solution chain_id hash 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 state seed_discriminant seed_challenge then ( + if eq_vdf_setup setup seed_discriminant seed_challenge then ( let* op_bytes = Plugin.RPC.Forge.vdf_revelation cctxt @@ -186,11 +183,10 @@ let inject_vdf_revelation cctxt state solution chain_id hash 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 level = +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 @@ -217,7 +213,7 @@ let fork_vdf_computation state discriminant challenge level = (* 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 {pid; ch_in} ; + state.computation_status <- Started (setup, {pid; ch_in}) ; let* () = emit_with_level (Printf.sprintf "Started to compute VDF, pid: %d" pid) @@ -227,7 +223,7 @@ let fork_vdf_computation state discriminant challenge level = (* 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 chain_id hash +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 @@ -249,9 +245,16 @@ let get_vdf_solution_if_ready cctxt state proc chain_id hash with | Ok solution -> let*! () = Lwt_io.close proc.ch_in in - state.computation_status <- Finished solution ; + state.computation_status <- Finished (setup, solution) ; let*! () = emit_with_level "Finished VDF computation" level in - inject_vdf_revelation cctxt state solution chain_id hash level_info + 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 ; @@ -295,7 +298,7 @@ let check_new_cycle state (level_info : Level.t) = let* () = match state.computation_status with | Injected -> return_unit - | Started proc -> + | Started ((_ : vdf_setup), proc) -> let*! () = kill_running_vdf_computation proc in emit_revelation_not_injected cycle | Not_started | Finished _ | Invalid -> @@ -359,14 +362,10 @@ let process_new_block (cctxt : #Protocol_client_context.full) state (* 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 vdf_setup = + let setup = Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge in - state.vdf_setup <- Some vdf_setup ; - let discriminant, challenge = vdf_setup in - let*! () = - fork_vdf_computation state discriminant challenge level - in + let*! () = fork_vdf_computation state setup level in return_unit | Computation_finished -> let*! () = @@ -379,7 +378,7 @@ let process_new_block (cctxt : #Protocol_client_context.full) state (* At this point the chain cannot be in the nonce revelation * stage. This is checked in [is_in_nonce_revelation_stage]. *) assert false) - | Started proc -> ( + | Started (setup, proc) -> ( let* seed_computation = get_seed_computation cctxt chain_id hash in match seed_computation with | Vdf_revelation_stage _ -> @@ -392,6 +391,7 @@ let process_new_block (cctxt : #Protocol_client_context.full) state cctxt state proc + setup chain_id hash level_info @@ -413,14 +413,21 @@ let process_new_block (cctxt : #Protocol_client_context.full) state (* At this point the chain cannot be in the nonce revelation * stage. This is checked in [is_in_nonce_revelation_stage]. *) assert false) - | Finished solution -> + | 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 solution chain_id hash level_info + inject_vdf_revelation + cctxt + state + setup + solution + chain_id + hash + level_info | Injected -> let*! () = emit_with_level "Skipping, VDF solution already injected" level @@ -444,7 +451,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 () -> -- GitLab From 3097399316ec7aef57588a9157f417f7bac0c752 Mon Sep 17 00:00:00 2001 From: victor-dumitrescu Date: Thu, 2 Feb 2023 18:13:53 +0100 Subject: [PATCH 4/5] Proto/VDF-daemon: helper functions in separate module --- src/proto_alpha/lib_delegate/baking_vdf.ml | 17 ++++------ src/proto_alpha/lib_delegate/vdf_helpers.ml | 31 +++++++++++++++++ src/proto_alpha/lib_delegate/vdf_helpers.mli | 35 ++++++++++++++++++++ 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 src/proto_alpha/lib_delegate/vdf_helpers.ml create mode 100644 src/proto_alpha/lib_delegate/vdf_helpers.mli diff --git a/src/proto_alpha/lib_delegate/baking_vdf.ml b/src/proto_alpha/lib_delegate/baking_vdf.ml index c1810513c250..504a3e581726 100644 --- a/src/proto_alpha/lib_delegate/baking_vdf.ml +++ b/src/proto_alpha/lib_delegate/baking_vdf.ml @@ -102,16 +102,13 @@ let get_level_info cctxt level = in return level_info -(* Determines whether the current block is in the nonce revelation stage by - * comparing its position in the cycle with the nonce revelation threshold, not - * by querying the [Seed_computation] RPC. *) -let is_in_nonce_revelation_stage 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 - in - let position_in_cycle = level_info.cycle_position in - return (Int32.compare position_in_cycle nonce_revelation_threshold < 0) + 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 *) @@ -345,7 +342,7 @@ let process_new_block (cctxt : #Protocol_client_context.full) state return_unit else (* If the chain is in the nonce revelation stage, there is nothing to do. *) - let* out = is_in_nonce_revelation_stage state level_info in + let* out = is_in_nonce_revelation_stage state.constants level_info in if out then let*! () = emit_with_level "Skipping, still in nonce revelation stage" level diff --git a/src/proto_alpha/lib_delegate/vdf_helpers.ml b/src/proto_alpha/lib_delegate/vdf_helpers.ml new file mode 100644 index 000000000000..b3fe451bc1d5 --- /dev/null +++ b/src/proto_alpha/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_alpha/lib_delegate/vdf_helpers.mli b/src/proto_alpha/lib_delegate/vdf_helpers.mli new file mode 100644 index 000000000000..1000c59bece0 --- /dev/null +++ b/src/proto_alpha/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 -- GitLab From 922bd06bfaa1074ebf8f480bb6c9f2495887626f Mon Sep 17 00:00:00 2001 From: victor-dumitrescu Date: Thu, 2 Feb 2023 18:14:52 +0100 Subject: [PATCH 5/5] Tezt/VDF: test helper functions from lib_delegate --- tezt/lib_tezos/vdf.ml | 7 ++ tezt/lib_tezos/vdf.mli | 6 ++ tezt/tests/vdf_test.ml | 208 ++++++++++++++++++++++++++++++----------- 3 files changed, 165 insertions(+), 56 deletions(-) diff --git a/tezt/lib_tezos/vdf.ml b/tezt/lib_tezos/vdf.ml index 525aed31326d..b5f579c01ab3 100644 --- a/tezt/lib_tezos/vdf.ml +++ b/tezt/lib_tezos/vdf.ml @@ -121,3 +121,10 @@ let restart vdf_baker = let* () = terminate vdf_baker in let* () = run vdf_baker in wait_for_ready vdf_baker + +module Helpers = struct + let is_in_nonce_revelation_stage ~nonce_revelation_threshold + ~(level : RPC.level) = + let cycle_position = Int32.of_int level.cycle_position in + Int32.compare cycle_position nonce_revelation_threshold < 0 +end diff --git a/tezt/lib_tezos/vdf.mli b/tezt/lib_tezos/vdf.mli index f1df5577e834..4e713a8391d5 100644 --- a/tezt/lib_tezos/vdf.mli +++ b/tezt/lib_tezos/vdf.mli @@ -58,3 +58,9 @@ val init : val restart : t -> unit Lwt.t val on_event : t -> (event -> unit) -> unit + +(** [Helpers] models functions from [Lib_delegate.Vdf_helpers]. *) +module Helpers : sig + val is_in_nonce_revelation_stage : + nonce_revelation_threshold:int32 -> level:RPC.level -> bool +end diff --git a/tezt/tests/vdf_test.ml b/tezt/tests/vdf_test.ml index b48a26d0df64..ab94327b2f31 100644 --- a/tezt/tests/vdf_test.ml +++ b/tezt/tests/vdf_test.ml @@ -58,70 +58,115 @@ let get_seed_computation_status ?(info = false) client level = if info then Log.info "At level %d we are in %s" level (pp_status status) ; return status -let assert_computation_status ?(info = false) client level status = - let* current_status = get_seed_computation_status ~info client level in - return @@ assert (current_status = status) +let assert_computation_status ?(info = false) ?(assert_is_not = false) + nonce_revelation_threshold client status = + let comp = if assert_is_not then ( <> ) else ( = ) in + let* level = + RPC.Client.call client @@ RPC.get_chain_block_helper_current_level () + in + let* current_status = get_seed_computation_status ~info client level.level in + (if current_status = Nonce_revelation_stage then + (* For levels in the nonce revelation stage, we also check the consistency + * of [Vdf.Helpers.is_in_nonce_revelation_stage] with + * the [Seed_computation] RPC. *) + let nonce_revelation_threshold = Int32.of_int nonce_revelation_threshold in + if + not + (Vdf.Helpers.is_in_nonce_revelation_stage + ~nonce_revelation_threshold + ~level) + then + failwith + (Printf.sprintf + "Vdf.Helpers.is_in_nonce_revelation_stage is inconsistent with the \ + Seed_computation RPC: returned false on level %d" + level.level)) ; + return @@ assert (comp current_status status) -let assert_level actual expected = - if actual <> expected then ( +let assert_not_computation_status = + assert_computation_status ~assert_is_not:true + +let assert_level client actual expected = + let* level = + RPC.Client.call client @@ RPC.get_chain_block_helper_current_level () + in + if actual <> expected || level.level <> expected then ( Log.info "Expected to be at level %d, actually at level %d" expected actual ; - assert false) - else () + return @@ assert false) + else Lwt.return_unit let init_vdf_event_listener vdf_baker injected = Vdf.on_event vdf_baker (fun Vdf.{name; _} -> if name = "vdf_revelation_injected.v0" then injected := true) -(* Bakes at most `max_level - min_starting_level + 1` blocks, starting from - * a level not lower than `min_starting_level` and finishing exactly - * at `max_level`. - * Optionally checks that the computation status is never `Computation_finished` +(* Bakes at most [max_level - min_starting_level + 1] blocks, starting from + * a level not lower than [min_starting_level] and finishing exactly + * at [max_level]. + * Optionally checks that the computation status is never [Computation_finished] * (used when checking a cycle with no VDF daemon running) *) -let bake_until min_starting_level max_level client node status_check = - let check_status level = - if status_check then - let* current_status = get_seed_computation_status client level in - return @@ assert (current_status <> Computation_finished) - else Lwt.return_unit - in +let bake_until min_starting_level max_level client node status_check + nonce_revelation_threshold = let rec loop level = if level < max_level then let* () = Client.bake_for client in let* level = Node.wait_for_level node (level + 1) in - let* () = check_status level in + let* () = + if status_check then + assert_not_computation_status + nonce_revelation_threshold + client + Computation_finished + else Lwt.return_unit + in loop level else return level in let* level = Node.wait_for_level node min_starting_level in let* level = loop level in let* level = Node.wait_for_level node level in - assert_level level max_level ; + let* () = assert_level client level max_level in return level -(* Checks that we are in the last block of the nonce revelation period, +(* Checks that we are in the last block of the nonce revelation stage, * sets an event handler for the VDF revelation operation, then bakes - * the whole of the VDF revelation period. Checks that (at least one) + * the whole of the VDF revelation stage. Checks that (at least one) * VDF revelation operation has been injected *) -let bake_vdf_revelation_period level max_level client node injected = +let bake_vdf_revelation_stage nonce_revelation_threshold level max_level client + node injected = injected := false ; - - let* () = assert_computation_status client level Nonce_revelation_stage in + let* () = + assert_computation_status + nonce_revelation_threshold + client + Nonce_revelation_stage + in let* () = Client.bake_for client in let* level = Node.wait_for_level node (level + 1) in - let* () = assert_computation_status client level Vdf_revelation_stage in - - let* _level = bake_until level max_level client node false in + let* () = + assert_computation_status + nonce_revelation_threshold + client + Vdf_revelation_stage + in + let* _level = + bake_until level max_level client node false nonce_revelation_threshold + in return @@ assert !injected let check_cycle (blocks_per_cycle, nonce_revelation_threshold) starting_level client node injected = (* Check that at the beginning of the cycle we are in the nonce - revelation period *) + revelation stage *) let* level = Node.wait_for_level node starting_level in - assert_level level starting_level ; - let* () = assert_computation_status client level Nonce_revelation_stage in + let* () = assert_level client level starting_level in + let* () = + assert_computation_status + nonce_revelation_threshold + client + Nonce_revelation_stage + in - (* Bake until the end of the nonce revelation period *) + (* Bake until the end of the nonce revelation stage *) let* level = bake_until level @@ -129,13 +174,15 @@ let check_cycle (blocks_per_cycle, nonce_revelation_threshold) starting_level client node false + nonce_revelation_threshold in - (* Bake throughout the VDF revelation period, checking that a VDF revelation + (* Bake throughout the VDF revelation stage, checking that a VDF revelation * operation is injected at some point. Check that the seed computation - * status is set to [Computation_finished] at the end of the period. *) + * status is set to [Computation_finished] at the end of the stage. *) let* () = - bake_vdf_revelation_period + bake_vdf_revelation_stage + nonce_revelation_threshold level (starting_level + blocks_per_cycle - 1) client @@ -145,14 +192,19 @@ let check_cycle (blocks_per_cycle, nonce_revelation_threshold) starting_level let* level = Node.wait_for_level node (starting_level + blocks_per_cycle - 1) in - assert_level level (starting_level + blocks_per_cycle - 1) ; - let* () = assert_computation_status client level Computation_finished in + let* () = assert_level client level (starting_level + blocks_per_cycle - 1) in + let* () = + assert_computation_status + nonce_revelation_threshold + client + Computation_finished + in (* Bake one more block, and check that it is the first block of * the following cycle *) let* () = Client.bake_for client in let* level = Node.wait_for_level node (starting_level + blocks_per_cycle) in - assert_level level (starting_level + blocks_per_cycle) ; + let* () = assert_level client level (starting_level + blocks_per_cycle) in return level let check_n_cycles n constants starting_level client node injected = @@ -164,7 +216,7 @@ let check_n_cycles n constants starting_level client node injected = in loop n starting_level -(* In total, [test_vdf] bakes `2 * (n_cycles + 1)` cycles *) +(* In total, [test_vdf] bakes [2 * (n_cycles + 1)] cycles *) let n_cycles = 3 let test_vdf : Protocol.t list -> unit = @@ -179,9 +231,9 @@ let test_vdf : Protocol.t list -> unit = let* () = Client.activate_protocol ~protocol client and* vdf_baker = Vdf.init ~protocol node in - (* Track whether a VDF revelation has been injected during the correct period. - * It is set to `false` at the beginning of [bake_vdf_revelation_period] and - * to `true` by a listener for `vdf_revelation_injected` events. *) + (* Track whether a VDF revelation has been injected during the correct stage. + * It is set to [false] at the beginning of [bake_vdf_revelation_stage] and + * to [true] by a listener for [vdf_revelation_injected] events. *) let injected = ref false in init_vdf_event_listener vdf_baker injected ; @@ -195,27 +247,51 @@ let test_vdf : Protocol.t list -> unit = return JSON.(constants |-> "nonce_revelation_threshold" |> as_int) in - (* Bake and check that we are in the nonce revelation period *) + (* Bake and check that we are in the nonce revelation stage *) let* () = Client.bake_for client in let* level = Node.wait_for_level node 1 in - let* () = assert_computation_status client level Nonce_revelation_stage in + let* () = + assert_computation_status + nonce_revelation_threshold + client + Nonce_revelation_stage + in - (* Bake until the end of the nonce revelation period *) - let* level = bake_until level nonce_revelation_threshold client node false in + (* Bake until the end of the nonce revelation stage *) + let* level = + bake_until + level + nonce_revelation_threshold + client + node + false + nonce_revelation_threshold + in (* Bake until the end of the cycle, checking that a VDF revelation - operation was injected during the VDF revelation period and that + operation was injected during the VDF revelation stage and that the computation status is set to finished at the end of the cycle *) let* () = - bake_vdf_revelation_period level blocks_per_cycle client node injected + bake_vdf_revelation_stage + nonce_revelation_threshold + level + blocks_per_cycle + client + node + injected + in + let* _level = Node.wait_for_level node blocks_per_cycle in + let* () = + assert_computation_status + nonce_revelation_threshold + client + Computation_finished in - let* level = Node.wait_for_level node blocks_per_cycle in - let* () = assert_computation_status client level Computation_finished in (* Bake, check that we are in the first block of the following cycle *) let* () = Client.bake_for client in let* level = Node.wait_for_level node (blocks_per_cycle + 1) in - assert_level level (blocks_per_cycle + 1) ; + let* () = assert_level client level (blocks_per_cycle + 1) in (* Check correct behaviour for the following cycles *) let* level = @@ -232,15 +308,29 @@ let test_vdf : Protocol.t list -> unit = Check that since no VDF daemon is running the computation status is never "finished" in this cycle. *) let* () = Vdf.terminate vdf_baker in - assert_level level ((blocks_per_cycle * (n_cycles + 1)) + 1) ; + let* () = + assert_level client level ((blocks_per_cycle * (n_cycles + 1)) + 1) + in let* level = - bake_until level ((blocks_per_cycle * (n_cycles + 2)) + 1) client node true + bake_until + level + ((blocks_per_cycle * (n_cycles + 2)) + 1) + client + node + true + nonce_revelation_threshold in (* Bake through most of a new cycle and restart the VDF daemon right before * the end of the cycle so that the VDF is computed too late for injection. *) let* level = - bake_until level ((blocks_per_cycle * (n_cycles + 3)) - 1) client node true + bake_until + level + ((blocks_per_cycle * (n_cycles + 3)) - 1) + client + node + true + nonce_revelation_threshold in let* vdf_baker = Vdf.init ~protocol node in init_vdf_event_listener vdf_baker injected ; @@ -249,10 +339,16 @@ let test_vdf : Protocol.t list -> unit = (* Bake to the end of the cycle and check that the VDF was not injected. *) let* level = - bake_until level ((blocks_per_cycle * (n_cycles + 3)) + 1) client node true + bake_until + level + ((blocks_per_cycle * (n_cycles + 3)) + 1) + client + node + true + nonce_revelation_threshold in - (* Check correct behaviour for another `n_cycles` after the RANDAO cycle and + (* Check correct behaviour for another [n_cycles] after the RANDAO cycle and * the failed injection cycle. *) let* _level = check_n_cycles -- GitLab