From 3cdb18e917c609a19d843f3b3525bbd7232d23b2 Mon Sep 17 00:00:00 2001 From: vbot Date: Wed, 22 Mar 2023 15:04:55 +0100 Subject: [PATCH 1/4] Alpha/Baker: fix delayed prequorum preventing endorsements --- .../lib_delegate/baking_actions.ml | 4 ++- .../lib_delegate/baking_scheduling.ml | 12 ++++++--- src/proto_alpha/lib_delegate/baking_state.ml | 24 +++++++++-------- src/proto_alpha/lib_delegate/baking_state.mli | 9 ++++--- .../lib_delegate/state_transitions.ml | 26 ++++++++++++++----- 5 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_actions.ml b/src/proto_alpha/lib_delegate/baking_actions.ml index da08e0e64f59..34b84b7d7377 100644 --- a/src/proto_alpha/lib_delegate/baking_actions.ml +++ b/src/proto_alpha/lib_delegate/baking_actions.ml @@ -755,7 +755,9 @@ let synchronize_round state {new_round_proposal; handle_proposal} = Round.pp new_round_proposal.block.round else - let new_round_state = {current_round; current_phase = Idle} in + let new_round_state = + {current_round; current_phase = Idle; delayed_prequorum = None} + in let new_state = {state with round_state = new_round_state} in handle_proposal new_state >>= return diff --git a/src/proto_alpha/lib_delegate/baking_scheduling.ml b/src/proto_alpha/lib_delegate/baking_scheduling.ml index 6613d073dac2..420d7be693dc 100644 --- a/src/proto_alpha/lib_delegate/baking_scheduling.ml +++ b/src/proto_alpha/lib_delegate/baking_scheduling.ml @@ -692,7 +692,6 @@ let create_initial_state cctxt ?(synchronize = true) ~chain config latest_proposal = current_proposal; is_latest_proposal_applied = true (* this proposal is expected to be the current head *); - delayed_prequorum = None; locked_round = None; endorsable_payload = None; elected_block; @@ -704,8 +703,15 @@ let create_initial_state cctxt ?(synchronize = true) ~chain config (if synchronize then create_round_durations constants >>? fun round_durations -> Baking_actions.compute_round current_proposal round_durations - >>? fun current_round -> ok {current_round; current_phase = Idle} - else ok {Baking_state.current_round = Round.zero; current_phase = Idle}) + >>? fun current_round -> + ok {current_round; current_phase = Idle; delayed_prequorum = None} + else + ok + { + Baking_state.current_round = Round.zero; + current_phase = Idle; + delayed_prequorum = None; + }) >>?= fun round_state -> let state = {global_state; level_state; round_state} in (* Try loading locked round and endorsable round from disk *) diff --git a/src/proto_alpha/lib_delegate/baking_state.ml b/src/proto_alpha/lib_delegate/baking_state.ml index 8a75551d3e22..77029832a4ef 100644 --- a/src/proto_alpha/lib_delegate/baking_state.ml +++ b/src/proto_alpha/lib_delegate/baking_state.ml @@ -288,8 +288,6 @@ type level_state = { current_level : int32; latest_proposal : proposal; is_latest_proposal_applied : bool; - delayed_prequorum : - (Operation_worker.candidate * Kind.preendorsement operation list) option; (* Last proposal received where we injected an endorsement (thus we have seen 2f+1 preendorsements) *) locked_round : locked_round option; @@ -339,7 +337,12 @@ let phase_encoding = (fun () -> Awaiting_endorsements); ] -type round_state = {current_round : Round.t; current_phase : phase} +type round_state = { + current_round : Round.t; + current_phase : phase; + delayed_prequorum : + (Operation_worker.candidate * Kind.preendorsement operation list) option; +} type state = { global_state : global_state; @@ -821,7 +824,6 @@ let pp_level_state fmt current_level; latest_proposal; is_latest_proposal_applied; - delayed_prequorum; locked_round; endorsable_payload; elected_block; @@ -831,13 +833,12 @@ let pp_level_state fmt } = Format.fprintf fmt - "@[Level state:@ current level: %ld@ @[proposal (applied:%b, \ - delayed prequorum:%b):@ %a@]@ locked round: %a@ endorsable payload: %a@ \ - elected block: %a@ @[own delegate slots:@ %a@]@ @[next level \ - own delegate slots:@ %a@]@ next level proposed round: %a@]" + "@[Level state:@ current level: %ld@ @[proposal (applied:%b):@ \ + %a@]@ locked round: %a@ endorsable payload: %a@ elected block: %a@ @[own delegate slots:@ %a@]@ @[next level own delegate slots:@ %a@]@ \ + next level proposed round: %a@]" current_level is_latest_proposal_applied - (Option.is_some delayed_prequorum) pp_proposal latest_proposal (pp_option pp_locked_round) @@ -859,14 +860,15 @@ let pp_phase fmt = function | Awaiting_application -> Format.fprintf fmt "awaiting application" | Awaiting_endorsements -> Format.fprintf fmt "awaiting endorsements" -let pp_round_state fmt {current_round; current_phase} = +let pp_round_state fmt {current_round; current_phase; delayed_prequorum} = Format.fprintf fmt - "@[Round state:@ round: %a@ phase: %a@]" + "@[Round state:@ round: %a,@ phase: %a,@ delayed prequorum: %b@]" Round.pp current_round pp_phase current_phase + (Option.is_some delayed_prequorum) let pp fmt {global_state; level_state; round_state} = Format.fprintf diff --git a/src/proto_alpha/lib_delegate/baking_state.mli b/src/proto_alpha/lib_delegate/baking_state.mli index 2eeb2e3253d5..c0340a6ddaa6 100644 --- a/src/proto_alpha/lib_delegate/baking_state.mli +++ b/src/proto_alpha/lib_delegate/baking_state.mli @@ -130,8 +130,6 @@ type level_state = { current_level : int32; latest_proposal : proposal; is_latest_proposal_applied : bool; - delayed_prequorum : - (Operation_worker.candidate * Kind.preendorsement operation list) option; locked_round : locked_round option; endorsable_payload : endorsable_payload option; elected_block : elected_block option; @@ -148,7 +146,12 @@ type phase = val phase_encoding : phase Data_encoding.t -type round_state = {current_round : Round.t; current_phase : phase} +type round_state = { + current_round : Round.t; + current_phase : phase; + delayed_prequorum : + (Operation_worker.candidate * Kind.preendorsement operation list) option; +} type state = { global_state : global_state; diff --git a/src/proto_alpha/lib_delegate/state_transitions.ml b/src/proto_alpha/lib_delegate/state_transitions.ml index c561684e9d56..88df8e76f37c 100644 --- a/src/proto_alpha/lib_delegate/state_transitions.ml +++ b/src/proto_alpha/lib_delegate/state_transitions.ml @@ -312,13 +312,14 @@ let rec handle_proposal ~is_proposal_applied state (new_proposal : proposal) = let new_level = new_proposal.block.shell.level in let compute_new_state ~current_round ~delegate_slots ~next_level_delegate_slots = - let round_state = {current_round; current_phase = Idle} in + let round_state = + {current_round; current_phase = Idle; delayed_prequorum = None} + in let level_state = { current_level = new_level; latest_proposal = new_proposal; is_latest_proposal_applied = is_proposal_applied; - delayed_prequorum = None; (* Unlock values *) locked_round = None; endorsable_payload = None; @@ -406,10 +407,10 @@ let may_register_early_prequorum state ((candidate, _) as received_prequorum) = >>= fun () -> do_nothing state else Events.(emit pqc_while_waiting_for_application candidate.hash) >>= fun () -> - let new_level_state = - {state.level_state with delayed_prequorum = Some received_prequorum} + let new_round_state = + {state.round_state with delayed_prequorum = Some received_prequorum} in - let new_state = {state with level_state = new_level_state} in + let new_state = {state with round_state = new_round_state} in do_nothing new_state (** In the association map [delegate_slots], the function returns an @@ -772,14 +773,25 @@ let handle_expected_applied_proposal (state : Baking_state.t) = {state.level_state with is_latest_proposal_applied = true} in let new_state = {state with level_state = new_level_state} in - match new_state.level_state.delayed_prequorum with + match new_state.round_state.delayed_prequorum with | None -> (* The application arrived before the prequorum: just wait for the prequorum. *) let new_state = update_current_phase new_state Awaiting_preendorsements in do_nothing new_state | Some (candidate, preendorsement_qc) -> (* The application arrived after the prequorum: handle the - prequorum received earlier. *) + prequorum received earlier. + Start by resetting the delayed_prequorum *) + let new_round_state = + {new_state.round_state with delayed_prequorum = None} + in + let new_state = + { + state with + level_state = new_level_state; + round_state = new_round_state; + } + in prequorum_reached_when_awaiting_preendorsements new_state candidate -- GitLab From 386ebd137bb20b90f8cac9de502df60d5a635a4a Mon Sep 17 00:00:00 2001 From: vbot Date: Wed, 22 Mar 2023 15:05:25 +0100 Subject: [PATCH 2/4] Alpha/Baker: test that delayed prequorum correctly reset --- .../lib_delegate/test/test_scenario.ml | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/proto_alpha/lib_delegate/test/test_scenario.ml b/src/proto_alpha/lib_delegate/test/test_scenario.ml index 6dcc3eac19ca..f890c5218452 100644 --- a/src/proto_alpha/lib_delegate/test/test_scenario.ml +++ b/src/proto_alpha/lib_delegate/test/test_scenario.ml @@ -113,6 +113,60 @@ let test_preendorse_on_valid () = let config = {default_config with timeout = 10} in run ~config [(1, (module Hooks))] +let test_reset_delayed_pqc () = + let module Hooks : Hooks = struct + include Default_hooks + + let should_wait = ref true + + let trigger = ref false + + let on_new_operation x = + (if !should_wait then Lwt_unix.sleep 0.5 else Lwt_unix.sleep 0.2) + >>= fun () -> + if !trigger then ( + trigger := false ; + Lwt.return_none) + else Lwt.return_some x + + let on_new_head ~block_hash ~(block_header : Block_header.t) = + let block_round = + match + Protocol.Alpha_context.Fitness.round_from_raw + block_header.shell.fitness + with + | Error _ -> assert false + | Ok x -> x + in + if + block_header.Block_header.shell.level = 1l + && Protocol.Alpha_context.Round.(block_round = zero) + then ( + Lwt_unix.sleep 1. >>= fun () -> + should_wait := false ; + trigger := true ; + Lwt.return_some (block_hash, block_header)) + else Lwt.return_some (block_hash, block_header) + + let stop_on_event = function + | Baking_state.New_valid_proposal {block; _} -> + let is_high_round = + let open Protocol.Alpha_context.Round in + match of_int 5 with + | Ok high_round -> block.round = high_round + | _ -> assert false + in + (block.shell.level = 1l && is_high_round) || block.shell.level > 1l + | _ -> false + + let check_chain_on_success ~chain = + let head = Stdlib.List.hd chain in + if head.rpc_context.block_header.level = 1l then failwith "baker is stuck" + else return_unit + end in + let config = {default_config with round0 = 2L; round1 = 3L; timeout = 50} in + run ~config [(1, (module Hooks))] + (* Scenario T1 @@ -1531,6 +1585,7 @@ let tests = [ tztest "reaches level 5" `Quick test_level_5; tztest "cannot progress without new head" `Quick test_preendorse_on_valid; + tztest "reset delayed pqc" `Quick test_reset_delayed_pqc; tztest "scenario t1" `Quick test_scenario_t1; tztest "scenario t2" `Quick test_scenario_t2; tztest "scenario t3" `Quick test_scenario_t3; -- GitLab From e0734d1adcbeb5e0f91f1ae1d677ff988488a452 Mon Sep 17 00:00:00 2001 From: vbot Date: Wed, 22 Mar 2023 15:05:43 +0100 Subject: [PATCH 3/4] Mumbai/Baker: backport delayed prequorum fix and test --- .../lib_delegate/baking_actions.ml | 4 +- .../lib_delegate/baking_scheduling.ml | 12 +++- .../lib_delegate/baking_state.ml | 23 ++++---- .../lib_delegate/baking_state.mli | 9 ++- .../lib_delegate/state_transitions.ml | 26 ++++++--- .../lib_delegate/test/test_scenario.ml | 55 +++++++++++++++++++ 6 files changed, 104 insertions(+), 25 deletions(-) diff --git a/src/proto_016_PtMumbai/lib_delegate/baking_actions.ml b/src/proto_016_PtMumbai/lib_delegate/baking_actions.ml index 7eb0bc6dd90d..3facc7abca2d 100644 --- a/src/proto_016_PtMumbai/lib_delegate/baking_actions.ml +++ b/src/proto_016_PtMumbai/lib_delegate/baking_actions.ml @@ -646,7 +646,9 @@ let synchronize_round state {new_round_proposal; handle_proposal} = Round.pp new_round_proposal.block.round else - let new_round_state = {current_round; current_phase = Idle} in + let new_round_state = + {current_round; current_phase = Idle; delayed_prequorum = None} + in let new_state = {state with round_state = new_round_state} in handle_proposal new_state >>= return diff --git a/src/proto_016_PtMumbai/lib_delegate/baking_scheduling.ml b/src/proto_016_PtMumbai/lib_delegate/baking_scheduling.ml index 3389440a8469..3a72ce87c0ea 100644 --- a/src/proto_016_PtMumbai/lib_delegate/baking_scheduling.ml +++ b/src/proto_016_PtMumbai/lib_delegate/baking_scheduling.ml @@ -678,7 +678,6 @@ let create_initial_state cctxt ?(synchronize = true) ~chain config latest_proposal = current_proposal; is_latest_proposal_applied = true (* this proposal is expected to be the current head *); - delayed_prequorum = None; injected_preendorsements = None; locked_round = None; endorsable_payload = None; @@ -691,8 +690,15 @@ let create_initial_state cctxt ?(synchronize = true) ~chain config (if synchronize then create_round_durations constants >>? fun round_durations -> Baking_actions.compute_round current_proposal round_durations - >>? fun current_round -> ok {current_round; current_phase = Idle} - else ok {Baking_state.current_round = Round.zero; current_phase = Idle}) + >>? fun current_round -> + ok {current_round; current_phase = Idle; delayed_prequorum = None} + else + ok + { + Baking_state.current_round = Round.zero; + current_phase = Idle; + delayed_prequorum = None; + }) >>?= fun round_state -> let state = {global_state; level_state; round_state} in (* Try loading locked round and endorsable round from disk *) diff --git a/src/proto_016_PtMumbai/lib_delegate/baking_state.ml b/src/proto_016_PtMumbai/lib_delegate/baking_state.ml index 4c3578740ae1..8785a1415378 100644 --- a/src/proto_016_PtMumbai/lib_delegate/baking_state.ml +++ b/src/proto_016_PtMumbai/lib_delegate/baking_state.ml @@ -280,8 +280,6 @@ type level_state = { current_level : int32; latest_proposal : proposal; is_latest_proposal_applied : bool; - delayed_prequorum : - (Operation_worker.candidate * Kind.preendorsement operation list) option; injected_preendorsements : packed_operation list option; (* Last proposal received where we injected an endorsement (thus we have seen 2f+1 preendorsements) *) @@ -332,7 +330,12 @@ let phase_encoding = (fun () -> Awaiting_endorsements); ] -type round_state = {current_round : Round.t; current_phase : phase} +type round_state = { + current_round : Round.t; + current_phase : phase; + delayed_prequorum : + (Operation_worker.candidate * Kind.preendorsement operation list) option; +} type state = { global_state : global_state; @@ -811,7 +814,6 @@ let pp_level_state fmt current_level; latest_proposal; is_latest_proposal_applied; - delayed_prequorum; injected_preendorsements; locked_round; endorsable_payload; @@ -823,13 +825,11 @@ let pp_level_state fmt Format.fprintf fmt "@[Level state:@ current level: %ld@ @[proposal (applied:%b, \ - delayed prequorum:%b, injected preendorsements: %d):@ %a@]@ locked round: \ - %a@ endorsable payload: %a@ elected block: %a@ @[own delegate \ - slots:@ %a@]@ @[next level own delegate slots:@ %a@]@ next level \ - proposed round: %a@]" + injected preendorsements: %d):@ %a@]@ locked round: %a@ endorsable \ + payload: %a@ elected block: %a@ @[own delegate slots:@ %a@]@ @[next level own delegate slots:@ %a@]@ next level proposed round: %a@]" current_level is_latest_proposal_applied - (Option.is_some delayed_prequorum) (match injected_preendorsements with None -> 0 | Some l -> List.length l) pp_proposal latest_proposal @@ -852,14 +852,15 @@ let pp_phase fmt = function | Awaiting_application -> Format.fprintf fmt "awaiting application" | Awaiting_endorsements -> Format.fprintf fmt "awaiting endorsements" -let pp_round_state fmt {current_round; current_phase} = +let pp_round_state fmt {current_round; current_phase; delayed_prequorum} = Format.fprintf fmt - "@[Round state:@ round: %a@ phase: %a@]" + "@[Round state:@ round: %a,@ phase: %a,@ delayed prequorum: %b@]" Round.pp current_round pp_phase current_phase + (Option.is_some delayed_prequorum) let pp fmt {global_state; level_state; round_state} = Format.fprintf diff --git a/src/proto_016_PtMumbai/lib_delegate/baking_state.mli b/src/proto_016_PtMumbai/lib_delegate/baking_state.mli index cfa439d81738..74d1fdc75f6e 100644 --- a/src/proto_016_PtMumbai/lib_delegate/baking_state.mli +++ b/src/proto_016_PtMumbai/lib_delegate/baking_state.mli @@ -128,8 +128,6 @@ type level_state = { current_level : int32; latest_proposal : proposal; is_latest_proposal_applied : bool; - delayed_prequorum : - (Operation_worker.candidate * Kind.preendorsement operation list) option; injected_preendorsements : packed_operation list option; locked_round : locked_round option; endorsable_payload : endorsable_payload option; @@ -147,7 +145,12 @@ type phase = val phase_encoding : phase Data_encoding.t -type round_state = {current_round : Round.t; current_phase : phase} +type round_state = { + current_round : Round.t; + current_phase : phase; + delayed_prequorum : + (Operation_worker.candidate * Kind.preendorsement operation list) option; +} type state = { global_state : global_state; diff --git a/src/proto_016_PtMumbai/lib_delegate/state_transitions.ml b/src/proto_016_PtMumbai/lib_delegate/state_transitions.ml index ab8f6d35d7ba..e5834c73c23d 100644 --- a/src/proto_016_PtMumbai/lib_delegate/state_transitions.ml +++ b/src/proto_016_PtMumbai/lib_delegate/state_transitions.ml @@ -312,13 +312,14 @@ let rec handle_proposal ~is_proposal_applied state (new_proposal : proposal) = let new_level = new_proposal.block.shell.level in let compute_new_state ~current_round ~delegate_slots ~next_level_delegate_slots = - let round_state = {current_round; current_phase = Idle} in + let round_state = + {current_round; current_phase = Idle; delayed_prequorum = None} + in let level_state = { current_level = new_level; latest_proposal = new_proposal; is_latest_proposal_applied = is_proposal_applied; - delayed_prequorum = None; injected_preendorsements = None; (* Unlock values *) locked_round = None; @@ -407,10 +408,10 @@ let may_register_early_prequorum state ((candidate, _) as received_prequorum) = >>= fun () -> do_nothing state else Events.(emit pqc_while_waiting_for_application candidate.hash) >>= fun () -> - let new_level_state = - {state.level_state with delayed_prequorum = Some received_prequorum} + let new_round_state = + {state.round_state with delayed_prequorum = Some received_prequorum} in - let new_state = {state with level_state = new_level_state} in + let new_state = {state with round_state = new_round_state} in do_nothing new_state (** In the association map [delegate_slots], the function returns an @@ -756,7 +757,7 @@ let handle_expected_applied_proposal (state : Baking_state.t) = {state.level_state with is_latest_proposal_applied = true} in let new_state = {state with level_state = new_level_state} in - match new_state.level_state.delayed_prequorum with + match new_state.round_state.delayed_prequorum with | None -> ( (* The application arrived before the prequorum: wait for the prequorum. *) let new_state = update_current_phase new_state Awaiting_preendorsements in @@ -777,7 +778,18 @@ let handle_expected_applied_proposal (state : Baking_state.t) = Lwt.return (new_state, reinject_preendorsement_action)) | Some (candidate, preendorsement_qc) -> (* The application arrived after the prequorum: handle the - prequorum received earlier. *) + prequorum received earlier. + Start by resetting the delayed_prequorum *) + let new_round_state = + {new_state.round_state with delayed_prequorum = None} + in + let new_state = + { + state with + level_state = new_level_state; + round_state = new_round_state; + } + in prequorum_reached_when_awaiting_preendorsements new_state candidate diff --git a/src/proto_016_PtMumbai/lib_delegate/test/test_scenario.ml b/src/proto_016_PtMumbai/lib_delegate/test/test_scenario.ml index 6dcc3eac19ca..f890c5218452 100644 --- a/src/proto_016_PtMumbai/lib_delegate/test/test_scenario.ml +++ b/src/proto_016_PtMumbai/lib_delegate/test/test_scenario.ml @@ -113,6 +113,60 @@ let test_preendorse_on_valid () = let config = {default_config with timeout = 10} in run ~config [(1, (module Hooks))] +let test_reset_delayed_pqc () = + let module Hooks : Hooks = struct + include Default_hooks + + let should_wait = ref true + + let trigger = ref false + + let on_new_operation x = + (if !should_wait then Lwt_unix.sleep 0.5 else Lwt_unix.sleep 0.2) + >>= fun () -> + if !trigger then ( + trigger := false ; + Lwt.return_none) + else Lwt.return_some x + + let on_new_head ~block_hash ~(block_header : Block_header.t) = + let block_round = + match + Protocol.Alpha_context.Fitness.round_from_raw + block_header.shell.fitness + with + | Error _ -> assert false + | Ok x -> x + in + if + block_header.Block_header.shell.level = 1l + && Protocol.Alpha_context.Round.(block_round = zero) + then ( + Lwt_unix.sleep 1. >>= fun () -> + should_wait := false ; + trigger := true ; + Lwt.return_some (block_hash, block_header)) + else Lwt.return_some (block_hash, block_header) + + let stop_on_event = function + | Baking_state.New_valid_proposal {block; _} -> + let is_high_round = + let open Protocol.Alpha_context.Round in + match of_int 5 with + | Ok high_round -> block.round = high_round + | _ -> assert false + in + (block.shell.level = 1l && is_high_round) || block.shell.level > 1l + | _ -> false + + let check_chain_on_success ~chain = + let head = Stdlib.List.hd chain in + if head.rpc_context.block_header.level = 1l then failwith "baker is stuck" + else return_unit + end in + let config = {default_config with round0 = 2L; round1 = 3L; timeout = 50} in + run ~config [(1, (module Hooks))] + (* Scenario T1 @@ -1531,6 +1585,7 @@ let tests = [ tztest "reaches level 5" `Quick test_level_5; tztest "cannot progress without new head" `Quick test_preendorse_on_valid; + tztest "reset delayed pqc" `Quick test_reset_delayed_pqc; tztest "scenario t1" `Quick test_scenario_t1; tztest "scenario t2" `Quick test_scenario_t2; tztest "scenario t3" `Quick test_scenario_t3; -- GitLab From 8ca38be78c9ab28858c57c5b65257e02216c007d Mon Sep 17 00:00:00 2001 From: vbot Date: Wed, 22 Mar 2023 15:11:37 +0100 Subject: [PATCH 4/4] Changelog: add a fix entry --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 078ef28d4761..a783e858a9bc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -60,6 +60,9 @@ Baker - Fixed a bug where the baker could count an (pre)endorsement twice while waiting for a (pre)quorum. +- Fixed a bug where receiving an early prequorum would made the baker + reach a state where it could not endorse anymore. + Accuser ------- -- GitLab