From 5edabcb0dbbad4d913e1231161419aab92f4cdea Mon Sep 17 00:00:00 2001 From: Raphael Toledo Date: Wed, 17 Aug 2022 19:03:00 +0100 Subject: [PATCH 1/4] Proto/baker: fix check_new_cycle in VDF daemon --- src/proto_014_PtKathma/lib_delegate/baking_vdf.ml | 2 +- src/proto_alpha/lib_delegate/baking_vdf.ml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml b/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml index 2e17f2c4ea31..fe7967963522 100644 --- a/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml +++ b/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml @@ -100,7 +100,7 @@ let check_new_cycle state level = match state.cycle with | None -> state.cycle <- Some current_cycle | Some cycle -> - if Cycle_repr.(succ cycle = current_cycle) then ( + if Cycle_repr.(succ cycle <= current_cycle) then ( state.cycle <- Some current_cycle ; state.computation_status <- Not_started) diff --git a/src/proto_alpha/lib_delegate/baking_vdf.ml b/src/proto_alpha/lib_delegate/baking_vdf.ml index 2e17f2c4ea31..fe7967963522 100644 --- a/src/proto_alpha/lib_delegate/baking_vdf.ml +++ b/src/proto_alpha/lib_delegate/baking_vdf.ml @@ -100,7 +100,7 @@ let check_new_cycle state level = match state.cycle with | None -> state.cycle <- Some current_cycle | Some cycle -> - if Cycle_repr.(succ cycle = current_cycle) then ( + if Cycle_repr.(succ cycle <= current_cycle) then ( state.cycle <- Some current_cycle ; state.computation_status <- Not_started) -- GitLab From 801f7fb534c7a49310cc7f72a3c03ce12def6b61 Mon Sep 17 00:00:00 2001 From: Victor Dumitrescu Date: Thu, 1 Sep 2022 16:53:45 +0200 Subject: [PATCH 2/4] Proto/baker: VDF daemon improvements and refactoring Co-authored-by: Raphael Toledo --- .../lib_delegate/baking_events.ml | 2 +- .../lib_delegate/baking_vdf.ml | 196 ++++++++++++++---- src/proto_alpha/lib_delegate/baking_events.ml | 2 +- src/proto_alpha/lib_delegate/baking_vdf.ml | 196 ++++++++++++++---- 4 files changed, 312 insertions(+), 84 deletions(-) diff --git a/src/proto_014_PtKathma/lib_delegate/baking_events.ml b/src/proto_014_PtKathma/lib_delegate/baking_events.ml index 979c85e08115..0cc63aaa9428 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 fe7967963522..f1cfa6c8b35d 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 stream_stopper : RPC_context.stopper option; mutable cycle : Cycle_repr.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 = @@ -81,6 +96,11 @@ let cycle_of_level state level = let level = Raw_level.to_int32 level in Int32.(div (pred level) blocks_per_cycle) +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 is_in_nonce_revelation_period state level = let { Constants.parametric = {blocks_per_cycle; nonce_revelation_threshold; _}; @@ -96,13 +116,32 @@ let is_in_nonce_revelation_period state level = Int32.compare position_in_cycle nonce_revelation_threshold <= 0 let check_new_cycle state level = + let open Lwt_result_syntax in let current_cycle = Cycle_repr.of_int32_exn (cycle_of_level state level) 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 ( + (* 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_repr.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,11 +158,28 @@ 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_str = Int32.to_string (Raw_level.to_int32 level) in - check_new_cycle state level ; + let* () = check_new_cycle state level in if Protocol_hash.(protocol <> next_protocol) then let*! () = D_Events.(emit protocol_change_detected) () in return_unit @@ -140,57 +196,114 @@ let process_new_block (cctxt : #Protocol_client_context.full) state | Started -> let*! () = Events.(emit vdf_info) - ("Already started VDF (level " ^ level_str ^ ")") + ("Skipping, 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 + 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 - state.computation_status <- Started ; - let discriminant, challenge = + let vdf_setup = Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge in - let solution = - Environment.Vdf.prove - discriminant - challenge - state.constants.parametric.vdf_difficulty + 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 - 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 -> + (* 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* op_hash = inject_vdf_revelation cctxt hash chain_id solution in - state.computation_status <- Injected ; + 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_of_level state level, + 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_revelation_injected) - (cycle_of_level state level, Chain_services.to_string chain, op_hash) + Events.(emit vdf_info) + ("Skipping, already injected VDF (level " ^ level_str ^ ")") in return_unit - | Injected -> + | Invalid -> let*! () = Events.(emit vdf_info) - ("Skipping, already injected VDF (level " ^ level_str ^ ")") + ("Skipping, failed to compute VDF (level " ^ level_str ^ ")") in return_unit @@ -205,13 +318,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 +339,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 9bdf35a9fefd..1719f257ab28 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 fe7967963522..f1cfa6c8b35d 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 stream_stopper : RPC_context.stopper option; mutable cycle : Cycle_repr.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 = @@ -81,6 +96,11 @@ let cycle_of_level state level = let level = Raw_level.to_int32 level in Int32.(div (pred level) blocks_per_cycle) +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 is_in_nonce_revelation_period state level = let { Constants.parametric = {blocks_per_cycle; nonce_revelation_threshold; _}; @@ -96,13 +116,32 @@ let is_in_nonce_revelation_period state level = Int32.compare position_in_cycle nonce_revelation_threshold <= 0 let check_new_cycle state level = + let open Lwt_result_syntax in let current_cycle = Cycle_repr.of_int32_exn (cycle_of_level state level) 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 ( + (* 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_repr.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,11 +158,28 @@ 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_str = Int32.to_string (Raw_level.to_int32 level) in - check_new_cycle state level ; + let* () = check_new_cycle state level in if Protocol_hash.(protocol <> next_protocol) then let*! () = D_Events.(emit protocol_change_detected) () in return_unit @@ -140,57 +196,114 @@ let process_new_block (cctxt : #Protocol_client_context.full) state | Started -> let*! () = Events.(emit vdf_info) - ("Already started VDF (level " ^ level_str ^ ")") + ("Skipping, 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 + 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 - state.computation_status <- Started ; - let discriminant, challenge = + let vdf_setup = Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge in - let solution = - Environment.Vdf.prove - discriminant - challenge - state.constants.parametric.vdf_difficulty + 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 - 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 -> + (* 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* op_hash = inject_vdf_revelation cctxt hash chain_id solution in - state.computation_status <- Injected ; + 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_of_level state level, + 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_revelation_injected) - (cycle_of_level state level, Chain_services.to_string chain, op_hash) + Events.(emit vdf_info) + ("Skipping, already injected VDF (level " ^ level_str ^ ")") in return_unit - | Injected -> + | Invalid -> let*! () = Events.(emit vdf_info) - ("Skipping, already injected VDF (level " ^ level_str ^ ")") + ("Skipping, failed to compute VDF (level " ^ level_str ^ ")") in return_unit @@ -205,13 +318,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 +339,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)) -> -- GitLab From 184c9cd629443b285f650fa886741094076172e7 Mon Sep 17 00:00:00 2001 From: Victor Dumitrescu Date: Thu, 1 Sep 2022 16:54:46 +0200 Subject: [PATCH 3/4] Test/tezt: adding a corner case to VDF daemon test --- tezt/tests/vdf_test.ml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tezt/tests/vdf_test.ml b/tezt/tests/vdf_test.ml index 5e0df4d5399c..e42078e7ea55 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 -- GitLab From 9c2eabb38f02f58bbeab300d36284e9928855695 Mon Sep 17 00:00:00 2001 From: Victor Dumitrescu Date: Fri, 2 Sep 2022 12:23:02 +0200 Subject: [PATCH 4/4] Proto/baker: Fix bug with getting cycle number in VDF daemon Co-authored-by: Pierre Boutillier --- .../lib_delegate/baking_vdf.ml | 279 +++++++++--------- src/proto_alpha/lib_delegate/baking_vdf.ml | 279 +++++++++--------- 2 files changed, 284 insertions(+), 274 deletions(-) diff --git a/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml b/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml index f1cfa6c8b35d..5a8b4aaabb79 100644 --- a/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml +++ b/src/proto_014_PtKathma/lib_delegate/baking_vdf.ml @@ -45,7 +45,7 @@ type 'a state = { constants : Constants.t; mutable block_stream : (block_info, 'a) result Lwt_stream.t; mutable stream_stopper : RPC_context.stopper option; - mutable cycle : Cycle_repr.t option; + mutable cycle : Cycle.t option; mutable computation_status : status; mutable vdf_setup : vdf_setup option; } @@ -91,39 +91,40 @@ 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 level = Raw_level.to_int32 level in - Int32.(div (pred level) blocks_per_cycle) - 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 is_in_nonce_revelation_period state level = - let { - Constants.parametric = {blocks_per_cycle; nonce_revelation_threshold; _}; - _; - } = - state.constants - in - let current_cycle = cycle_of_level state level in +let get_level_info cctxt level = + let open Lwt_result_syntax in let level = Raw_level.to_int32 level in - let position_in_cycle = - Int32.(sub level (mul current_cycle 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_info : Level.t) = + let open Lwt_result_syntax in + let {Constants.parametric = {nonce_revelation_threshold; _}; _} = + state.constants 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 check_new_cycle state (level_info : Level.t) = let open Lwt_result_syntax in - let current_cycle = Cycle_repr.of_int32_exn (cycle_of_level state level) in + let current_cycle = level_info.cycle in match state.cycle with | 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. *) @@ -131,7 +132,7 @@ let check_new_cycle state level = match state.computation_status with | Injected -> return_unit | _ -> - let cycle_str = Int32.to_string (Cycle_repr.to_int32 cycle) in + 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) @@ -178,134 +179,138 @@ let eq_vdf_setup state seed_discriminant seed_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 - let* () = check_new_cycle state level in + 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) - ("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* 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_revelation_injected) - ( cycle_of_level state level, - Chain_services.to_string chain, - op_hash ) + 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) - else ( - state.computation_status <- Invalid ; + | 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: setup has been updated (level " - ^ level_str ^ ")") + ("Error injecting VDF: already injected (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 + | 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 = diff --git a/src/proto_alpha/lib_delegate/baking_vdf.ml b/src/proto_alpha/lib_delegate/baking_vdf.ml index f1cfa6c8b35d..5a8b4aaabb79 100644 --- a/src/proto_alpha/lib_delegate/baking_vdf.ml +++ b/src/proto_alpha/lib_delegate/baking_vdf.ml @@ -45,7 +45,7 @@ type 'a state = { constants : Constants.t; mutable block_stream : (block_info, 'a) result Lwt_stream.t; mutable stream_stopper : RPC_context.stopper option; - mutable cycle : Cycle_repr.t option; + mutable cycle : Cycle.t option; mutable computation_status : status; mutable vdf_setup : vdf_setup option; } @@ -91,39 +91,40 @@ 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 level = Raw_level.to_int32 level in - Int32.(div (pred level) blocks_per_cycle) - 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 is_in_nonce_revelation_period state level = - let { - Constants.parametric = {blocks_per_cycle; nonce_revelation_threshold; _}; - _; - } = - state.constants - in - let current_cycle = cycle_of_level state level in +let get_level_info cctxt level = + let open Lwt_result_syntax in let level = Raw_level.to_int32 level in - let position_in_cycle = - Int32.(sub level (mul current_cycle 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_info : Level.t) = + let open Lwt_result_syntax in + let {Constants.parametric = {nonce_revelation_threshold; _}; _} = + state.constants 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 check_new_cycle state (level_info : Level.t) = let open Lwt_result_syntax in - let current_cycle = Cycle_repr.of_int32_exn (cycle_of_level state level) in + let current_cycle = level_info.cycle in match state.cycle with | 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. *) @@ -131,7 +132,7 @@ let check_new_cycle state level = match state.computation_status with | Injected -> return_unit | _ -> - let cycle_str = Int32.to_string (Cycle_repr.to_int32 cycle) in + 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) @@ -178,134 +179,138 @@ let eq_vdf_setup state seed_discriminant seed_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 - let* () = check_new_cycle state level in + 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) - ("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* 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_revelation_injected) - ( cycle_of_level state level, - Chain_services.to_string chain, - op_hash ) + 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) - else ( - state.computation_status <- Invalid ; + | 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: setup has been updated (level " - ^ level_str ^ ")") + ("Error injecting VDF: already injected (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 + | 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 = -- GitLab