diff --git a/src/proto_014_PtKathma/lib_delegate/baking_events.ml b/src/proto_014_PtKathma/lib_delegate/baking_events.ml index 979c85e0811577a0899842bfc20f4351e83667c4..0cc63aaa94287176731c52bbafd3a9ec926de530 100644 --- a/src/proto_014_PtKathma/lib_delegate/baking_events.ml +++ b/src/proto_014_PtKathma/lib_delegate/baking_events.ml @@ -717,7 +717,7 @@ module VDF = struct declare_1 ~section ~name:"vdf_internal" - ~level:Debug + ~level:Notice ~msg:"{msg}" ("msg", Data_encoding.string) end diff --git a/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml b/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml index 2e17f2c4ea31df616d89c84c2f96a73e748b135a..5a8b4aaabb795629bcd394a707d2b12c5ae69c04 100644 --- a/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml +++ b/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml @@ -29,17 +29,25 @@ open Client_baking_blocks module Events = Baking_events.VDF module D_Events = Delegate_events.Denunciator -type vdf_solution = Environment.Vdf.result * Environment.Vdf.proof +type vdf_solution = Seed_repr.vdf_solution -type status = Not_started | Started | Finished of vdf_solution | Injected +type vdf_setup = Seed_repr.vdf_setup + +type status = + | Not_started + | Started + | Finished of vdf_solution + | Injected + | Invalid type 'a state = { cctxt : Protocol_client_context.full; constants : Constants.t; mutable block_stream : (block_info, 'a) result Lwt_stream.t; - mutable stream_stopper : RPC_context.stopper; - mutable cycle : Cycle_repr.t option; + mutable stream_stopper : 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 = @@ -49,24 +57,31 @@ let init_block_stream_with_stopper cctxt chain = ~chains:[chain] () +let stop_block_stream state = + Option.iter + (fun stopper -> + stopper () ; + state.stream_stopper <- None) + state.stream_stopper + let restart_block_stream cctxt chain state = let open Lwt_result_syntax in - state.stream_stopper () ; + stop_block_stream state ; let retries_on_failure = 10 in - let rec try_start_block_stream cctxt chain state retries_on_failure = + 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 <- stream_stopper ; + 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 cctxt chain state (retries_on_failure - 1) + try_start_block_stream (retries_on_failure - 1) else fail e in - let* () = try_start_block_stream cctxt chain state retries_on_failure in + let* () = try_start_block_stream retries_on_failure in return_unit let log_errors_and_continue ~name p = @@ -76,33 +91,58 @@ let log_errors_and_continue ~name p = | Ok () -> return_unit | Error errs -> Events.(emit vdf_daemon_error) (name, errs) -let cycle_of_level state level = - let {Constants.parametric = {blocks_per_cycle; _}; _} = state.constants in +let get_seed_computation cctxt chain_id hash = + let chain = `Hash chain_id in + let block = `Hash (hash, 0) in + Alpha_services.Seed_computation.get cctxt (chain, block) + +let get_level_info cctxt level = + let open Lwt_result_syntax in let level = Raw_level.to_int32 level in - Int32.(div (pred level) blocks_per_cycle) + let* {protocol_data = {level_info; _}; _} = + Protocol_client_context.Alpha_block_services.metadata + cctxt + ~chain:cctxt#chain + ~block:(`Level level) + () + in + return level_info -let is_in_nonce_revelation_period state level = - let { - Constants.parametric = {blocks_per_cycle; nonce_revelation_threshold; _}; - _; - } = +let is_in_nonce_revelation_period state (level_info : Level.t) = + let open Lwt_result_syntax in + let {Constants.parametric = {nonce_revelation_threshold; _}; _} = state.constants in - let current_cycle = cycle_of_level state level in - let level = Raw_level.to_int32 level in - let position_in_cycle = - Int32.(sub level (mul current_cycle blocks_per_cycle)) - in - Int32.compare position_in_cycle nonce_revelation_threshold <= 0 + let position_in_cycle = level_info.cycle_position in + return (Int32.compare position_in_cycle nonce_revelation_threshold < 0) -let check_new_cycle state level = - let current_cycle = Cycle_repr.of_int32_exn (cycle_of_level state level) in +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 -> state.cycle <- Some current_cycle + | None -> + state.cycle <- Some current_cycle ; + return_unit | Some cycle -> - if Cycle_repr.(succ cycle = current_cycle) then ( + if Cycle.(succ 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. *) + 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 + in state.cycle <- Some current_cycle ; - state.computation_status <- Not_started) + 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 @@ -119,80 +159,158 @@ let inject_vdf_revelation cctxt hash chain_id solution = 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) + 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 - check_new_cycle state level ; + let* () = check_new_cycle state level_info in if Protocol_hash.(protocol <> next_protocol) then let*! () = D_Events.(emit protocol_change_detected) () in return_unit - else if is_in_nonce_revelation_period state level then - let*! () = - Events.(emit vdf_info) - ("Skipping, still in nonce revelation period (level " ^ level_str ^ ")") - in - return_unit - (* enter main loop if we are not in the nonce revelation period and - the expected protocol has been activated *) else - match state.computation_status with - | Started -> - let*! () = - Events.(emit vdf_info) - ("Already started VDF (level " ^ level_str ^ ")") - in - return_unit - | Not_started -> ( - let chain = `Hash chain_id in - let block = `Hash (hash, 0) in - let* seed_computation = - Alpha_services.Seed_computation.get cctxt (chain, block) - in - match seed_computation with - | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> - let*! () = - Events.(emit vdf_info) - ("Started to compute VDF (level " ^ level_str ^ ")") - in - state.computation_status <- Started ; - let discriminant, challenge = - Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge - in - let solution = - Environment.Vdf.prove - discriminant - challenge - state.constants.parametric.vdf_difficulty - in - state.computation_status <- Finished solution ; - - (* `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. *) - restart_block_stream cctxt chain state - | Nonce_revelation_stage | Computation_finished -> - (* this should never actually happen if computation - has not been started *) - assert false) - | Finished solution -> - let*! () = - Events.(emit vdf_info) ("Finished VDF (level " ^ level_str ^ ")") - in - let chain = `Hash chain_id in - let* op_hash = inject_vdf_revelation cctxt hash chain_id solution in - state.computation_status <- Injected ; - let*! () = - Events.(emit vdf_revelation_injected) - (cycle_of_level state level, Chain_services.to_string chain, op_hash) - in - return_unit - | Injected -> - let*! () = - Events.(emit vdf_info) - ("Skipping, already injected VDF (level " ^ level_str ^ ")") - in - return_unit + let* out = is_in_nonce_revelation_period state level_info in + if out then + let*! () = + Events.(emit vdf_info) + ("Skipping, still in nonce revelation period (level " ^ level_str + ^ ")") + in + return_unit + (* enter main loop if we are not in the nonce revelation period and + the expected protocol has been activated *) + else + 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 = + 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) + 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 + 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 + ^ ")") + in + return_unit + | Computation_finished -> + state.computation_status <- Injected ; + let*! () = + Events.(emit vdf_info) + ("Error injecting VDF: already injected (level " ^ level_str + ^ ")") + in + return_unit) + | Injected -> + let*! () = + Events.(emit vdf_info) + ("Skipping, already injected VDF (level " ^ level_str ^ ")") + in + return_unit + | Invalid -> + let*! () = + Events.(emit vdf_info) + ("Skipping, failed to compute VDF (level " ^ level_str ^ ")") + in + return_unit let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants chain = @@ -205,13 +323,14 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants cctxt; constants; block_stream; - stream_stopper; + stream_stopper = Some stream_stopper; cycle = None; computation_status = Not_started; + vdf_setup = None; } in Lwt_canceler.on_cancel canceler (fun () -> - state.stream_stopper () ; + stop_block_stream state ; Lwt.return_unit) ; let rec worker_loop () = let*! b = @@ -225,7 +344,7 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants | `Termination -> return_unit | `Block (None | Some (Error _)) -> (* exit when the node is unavailable *) - state.stream_stopper () ; + stop_block_stream state ; let*! () = Events.(emit vdf_daemon_connection_lost) name in tzfail Baking_errors.Node_connection_lost | `Block (Some (Ok bi)) -> diff --git a/src/proto_alpha/lib_delegate/baking_events.ml b/src/proto_alpha/lib_delegate/baking_events.ml index 9bdf35a9fefdce9d720952c45f2cecd08bcbb34a..1719f257ab28ff16ef92f29c38a8e1651981f24a 100644 --- a/src/proto_alpha/lib_delegate/baking_events.ml +++ b/src/proto_alpha/lib_delegate/baking_events.ml @@ -713,7 +713,7 @@ module VDF = struct declare_1 ~section ~name:"vdf_internal" - ~level:Debug + ~level:Notice ~msg:"{msg}" ("msg", Data_encoding.string) end diff --git a/src/proto_alpha/lib_delegate/baking_vdf.ml b/src/proto_alpha/lib_delegate/baking_vdf.ml index 2e17f2c4ea31df616d89c84c2f96a73e748b135a..5a8b4aaabb795629bcd394a707d2b12c5ae69c04 100644 --- a/src/proto_alpha/lib_delegate/baking_vdf.ml +++ b/src/proto_alpha/lib_delegate/baking_vdf.ml @@ -29,17 +29,25 @@ open Client_baking_blocks module Events = Baking_events.VDF module D_Events = Delegate_events.Denunciator -type vdf_solution = Environment.Vdf.result * Environment.Vdf.proof +type vdf_solution = Seed_repr.vdf_solution -type status = Not_started | Started | Finished of vdf_solution | Injected +type vdf_setup = Seed_repr.vdf_setup + +type status = + | Not_started + | Started + | Finished of vdf_solution + | Injected + | Invalid type 'a state = { cctxt : Protocol_client_context.full; constants : Constants.t; mutable block_stream : (block_info, 'a) result Lwt_stream.t; - mutable stream_stopper : RPC_context.stopper; - mutable cycle : Cycle_repr.t option; + mutable stream_stopper : 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 = @@ -49,24 +57,31 @@ let init_block_stream_with_stopper cctxt chain = ~chains:[chain] () +let stop_block_stream state = + Option.iter + (fun stopper -> + stopper () ; + state.stream_stopper <- None) + state.stream_stopper + let restart_block_stream cctxt chain state = let open Lwt_result_syntax in - state.stream_stopper () ; + stop_block_stream state ; let retries_on_failure = 10 in - let rec try_start_block_stream cctxt chain state retries_on_failure = + 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 <- stream_stopper ; + 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 cctxt chain state (retries_on_failure - 1) + try_start_block_stream (retries_on_failure - 1) else fail e in - let* () = try_start_block_stream cctxt chain state retries_on_failure in + let* () = try_start_block_stream retries_on_failure in return_unit let log_errors_and_continue ~name p = @@ -76,33 +91,58 @@ let log_errors_and_continue ~name p = | Ok () -> return_unit | Error errs -> Events.(emit vdf_daemon_error) (name, errs) -let cycle_of_level state level = - let {Constants.parametric = {blocks_per_cycle; _}; _} = state.constants in +let get_seed_computation cctxt chain_id hash = + let chain = `Hash chain_id in + let block = `Hash (hash, 0) in + Alpha_services.Seed_computation.get cctxt (chain, block) + +let get_level_info cctxt level = + let open Lwt_result_syntax in let level = Raw_level.to_int32 level in - Int32.(div (pred level) blocks_per_cycle) + let* {protocol_data = {level_info; _}; _} = + Protocol_client_context.Alpha_block_services.metadata + cctxt + ~chain:cctxt#chain + ~block:(`Level level) + () + in + return level_info -let is_in_nonce_revelation_period state level = - let { - Constants.parametric = {blocks_per_cycle; nonce_revelation_threshold; _}; - _; - } = +let is_in_nonce_revelation_period state (level_info : Level.t) = + let open Lwt_result_syntax in + let {Constants.parametric = {nonce_revelation_threshold; _}; _} = state.constants in - let current_cycle = cycle_of_level state level in - let level = Raw_level.to_int32 level in - let position_in_cycle = - Int32.(sub level (mul current_cycle blocks_per_cycle)) - in - Int32.compare position_in_cycle nonce_revelation_threshold <= 0 + let position_in_cycle = level_info.cycle_position in + return (Int32.compare position_in_cycle nonce_revelation_threshold < 0) -let check_new_cycle state level = - let current_cycle = Cycle_repr.of_int32_exn (cycle_of_level state level) in +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 -> state.cycle <- Some current_cycle + | None -> + state.cycle <- Some current_cycle ; + return_unit | Some cycle -> - if Cycle_repr.(succ cycle = current_cycle) then ( + if Cycle.(succ 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. *) + 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 + in state.cycle <- Some current_cycle ; - state.computation_status <- Not_started) + 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 @@ -119,80 +159,158 @@ let inject_vdf_revelation cctxt hash chain_id solution = 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) + 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 - check_new_cycle state level ; + let* () = check_new_cycle state level_info in if Protocol_hash.(protocol <> next_protocol) then let*! () = D_Events.(emit protocol_change_detected) () in return_unit - else if is_in_nonce_revelation_period state level then - let*! () = - Events.(emit vdf_info) - ("Skipping, still in nonce revelation period (level " ^ level_str ^ ")") - in - return_unit - (* enter main loop if we are not in the nonce revelation period and - the expected protocol has been activated *) else - match state.computation_status with - | Started -> - let*! () = - Events.(emit vdf_info) - ("Already started VDF (level " ^ level_str ^ ")") - in - return_unit - | Not_started -> ( - let chain = `Hash chain_id in - let block = `Hash (hash, 0) in - let* seed_computation = - Alpha_services.Seed_computation.get cctxt (chain, block) - in - match seed_computation with - | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> - let*! () = - Events.(emit vdf_info) - ("Started to compute VDF (level " ^ level_str ^ ")") - in - state.computation_status <- Started ; - let discriminant, challenge = - Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge - in - let solution = - Environment.Vdf.prove - discriminant - challenge - state.constants.parametric.vdf_difficulty - in - state.computation_status <- Finished solution ; - - (* `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. *) - restart_block_stream cctxt chain state - | Nonce_revelation_stage | Computation_finished -> - (* this should never actually happen if computation - has not been started *) - assert false) - | Finished solution -> - let*! () = - Events.(emit vdf_info) ("Finished VDF (level " ^ level_str ^ ")") - in - let chain = `Hash chain_id in - let* op_hash = inject_vdf_revelation cctxt hash chain_id solution in - state.computation_status <- Injected ; - let*! () = - Events.(emit vdf_revelation_injected) - (cycle_of_level state level, Chain_services.to_string chain, op_hash) - in - return_unit - | Injected -> - let*! () = - Events.(emit vdf_info) - ("Skipping, already injected VDF (level " ^ level_str ^ ")") - in - return_unit + let* out = is_in_nonce_revelation_period state level_info in + if out then + let*! () = + Events.(emit vdf_info) + ("Skipping, still in nonce revelation period (level " ^ level_str + ^ ")") + in + return_unit + (* enter main loop if we are not in the nonce revelation period and + the expected protocol has been activated *) + else + 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 = + 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) + 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 + 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 + ^ ")") + in + return_unit + | Computation_finished -> + state.computation_status <- Injected ; + let*! () = + Events.(emit vdf_info) + ("Error injecting VDF: already injected (level " ^ level_str + ^ ")") + in + return_unit) + | Injected -> + let*! () = + Events.(emit vdf_info) + ("Skipping, already injected VDF (level " ^ level_str ^ ")") + in + return_unit + | Invalid -> + let*! () = + Events.(emit vdf_info) + ("Skipping, failed to compute VDF (level " ^ level_str ^ ")") + in + return_unit let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants chain = @@ -205,13 +323,14 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants cctxt; constants; block_stream; - stream_stopper; + stream_stopper = Some stream_stopper; cycle = None; computation_status = Not_started; + vdf_setup = None; } in Lwt_canceler.on_cancel canceler (fun () -> - state.stream_stopper () ; + stop_block_stream state ; Lwt.return_unit) ; let rec worker_loop () = let*! b = @@ -225,7 +344,7 @@ let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants | `Termination -> return_unit | `Block (None | Some (Error _)) -> (* exit when the node is unavailable *) - state.stream_stopper () ; + stop_block_stream state ; let*! () = Events.(emit vdf_daemon_connection_lost) name in tzfail Baking_errors.Node_connection_lost | `Block (Some (Ok bi)) -> diff --git a/tezt/tests/vdf_test.ml b/tezt/tests/vdf_test.ml index 5e0df4d5399c72d6f7fa688fe45cc944eba5b7ac..e42078e7ea55a81e54024f76e55e53aab649582d 100644 --- a/tezt/tests/vdf_test.ml +++ b/tezt/tests/vdf_test.ml @@ -165,7 +165,7 @@ let check_n_cycles n constants starting_level client node injected = loop n starting_level (* In total, [test_vdf] bakes `2 * (n_cycles + 1)` cycles *) -let n_cycles = 5 +let n_cycles = 3 let test_vdf : Protocol.t list -> unit = (* [check_n_cycles] requires that the starting_level is the beginning of @@ -237,10 +237,21 @@ let test_vdf : Protocol.t list -> unit = bake_until level ((blocks_per_cycle * (n_cycles + 2)) + 1) client node true in - (* Restart a VDF daemon and check correct behaviour after a RANDAO cycle *) + (* 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 + in let* vdf_baker = Vdf.init ~protocol node in init_vdf_event_listener vdf_baker injected ; + (* 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 + in + + (* Check correct behaviour for another `n_cycles` after the RANDAO cycle and + * the failed injection cycle. *) let* _level = check_n_cycles n_cycles