From cd876359ad623d03f0fb71240db9dec9b5ec710d Mon Sep 17 00:00:00 2001 From: Albin Coquereau Date: Thu, 11 Jul 2024 10:27:00 +0200 Subject: [PATCH 1/3] paris/baker: rework attestation injection Co-authored-by: Julien Tesson --- .../lib_delegate/baking_events.ml | 35 ++++++ .../lib_delegate/state_transitions.ml | 110 +++++++++++++++--- 2 files changed, 126 insertions(+), 19 deletions(-) diff --git a/src/proto_020_PsParisC/lib_delegate/baking_events.ml b/src/proto_020_PsParisC/lib_delegate/baking_events.ml index 6a5cb7612c77..36c0b3a8c9c2 100644 --- a/src/proto_020_PsParisC/lib_delegate/baking_events.ml +++ b/src/proto_020_PsParisC/lib_delegate/baking_events.ml @@ -379,6 +379,41 @@ module State_transitions = struct ("level", Data_encoding.int32) ~pp3:Round.pp ("round", Round.encoding) + + let discarding_unexpected_attestation_without_prequorum_payload = + declare_3 + ~section + ~name:"discarding_unexpected_attestation_without_prequorum" + ~level:Warning + ~msg: + "discarding attestation for {delegate} at level {level}, round {round} \ + where no prequorum was reached." + ~pp1:Baking_state.pp_consensus_key_and_delegate + ("delegate", Baking_state.consensus_key_and_delegate_encoding) + ~pp2:pp_int32 + ("level", Data_encoding.int32) + ~pp3:Round.pp + ("round", Round.encoding) + + let discarding_unexpected_attestation_with_different_prequorum_payload = + declare_5 + ~section + ~name:"discarding_unexpected_attestation_with_different_prequorum" + ~level:Warning + ~msg: + "discarding attestation for {delegate} with payload {payload} at level \ + {level}, round {round} where the prequorum was on a different \ + payload {state_payload}." + ~pp1:Baking_state.pp_consensus_key_and_delegate + ("delegate", Baking_state.consensus_key_and_delegate_encoding) + ~pp2:Block_payload_hash.pp + ("payload", Block_payload_hash.encoding) + ~pp3:pp_int32 + ("level", Data_encoding.int32) + ~pp4:Round.pp + ("round", Round.encoding) + ~pp5:Block_payload_hash.pp + ("state_payload", Block_payload_hash.encoding) end module Node_rpc = struct diff --git a/src/proto_020_PsParisC/lib_delegate/state_transitions.ml b/src/proto_020_PsParisC/lib_delegate/state_transitions.ml index 7610ea010eb7..4e0a3a29cfd2 100644 --- a/src/proto_020_PsParisC/lib_delegate/state_transitions.ml +++ b/src/proto_020_PsParisC/lib_delegate/state_transitions.ml @@ -741,13 +741,71 @@ let prepare_attest_action state proposal = in Prepare_attestations {attestations} -let inject_early_arrived_attestations state = - let early_attestations = state.round_state.early_attestations in - match early_attestations with - | [] -> Lwt.return (state, Watch_quorum) - | first_signed_attestation :: _ as unbatched_signed_attestations -> ( - let new_round_state = {state.round_state with early_attestations = []} in - let new_state = {state with round_state = new_round_state} in +(* This function is called once a prequorum has been reached. *) +let may_inject_attestations state ~first_signed_attestation + ~other_signed_attestations = + let open Lwt_syntax in + let emit_discarding_unexpected_attestation_event + ?(payload : attestable_payload option) attestation = + let { + vote_consensus_content = {level; round = att_round; block_payload_hash; _}; + delegate; + _; + } = + attestation.unsigned_consensus_vote + in + let att_level = Raw_level.to_int32 level in + match payload with + | None -> + Events.( + emit + discarding_unexpected_attestation_without_prequorum_payload + (delegate, att_level, att_round)) + | Some payload -> + Events.( + emit + discarding_unexpected_attestation_with_different_prequorum_payload + ( delegate, + block_payload_hash, + att_level, + att_round, + payload.proposal.block.payload_hash )) + in + let check_payload state attestation do_action = + match state.level_state.attestable_payload with + | None -> + (* No attestable payload, either the prequorum has not been reached yet, + or an other issue occurred, we cannot inject the attestations. *) + let* () = emit_discarding_unexpected_attestation_event attestation in + do_nothing state + | Some payload -> + if + not + Block_payload_hash.( + payload.proposal.block.payload_hash + = attestation.unsigned_consensus_vote.vote_consensus_content + .block_payload_hash) + then + (* Attestable payload found in the state but it is different from the + one in the attestation operation, we cannot inject the + attestation. *) + let* () = + emit_discarding_unexpected_attestation_event ~payload attestation + in + do_nothing state + else do_action + in + match other_signed_attestations with + | [] -> + check_payload state first_signed_attestation + @@ + let signed_attestations = + make_singleton_consensus_vote_batch first_signed_attestation + in + Lwt.return (state, Inject_attestations {signed_attestations}) + | _ :: _ -> ( + check_payload state first_signed_attestation + @@ let batch_branch = get_branch_from_proposal state.level_state.latest_proposal in @@ -766,11 +824,25 @@ let inject_early_arrived_attestations state = Attestation batch_content ~batch_branch - unbatched_signed_attestations + (first_signed_attestation :: other_signed_attestations) |> function | Ok signed_attestations -> - Lwt.return (new_state, Inject_attestations {signed_attestations}) - | Error _err -> (* Unreachable *) do_nothing new_state) + Lwt.return (state, Inject_attestations {signed_attestations}) + | Error _err -> (* Unreachable *) do_nothing state) + +(* This function tries to inject attestations already prepared if the + prequorum is reached. *) +let may_inject_early_forged_attestations state = + let early_attestations = state.round_state.early_attestations in + match early_attestations with + | [] -> Lwt.return (state, Watch_quorum) + | first_signed_attestation :: other_signed_attestations -> + let new_round_state = {state.round_state with early_attestations = []} in + let new_state = {state with round_state = new_round_state} in + may_inject_attestations + new_state + ~first_signed_attestation + ~other_signed_attestations let prequorum_reached_when_awaiting_preattestations state candidate preattestations = @@ -828,7 +900,7 @@ let prequorum_reached_when_awaiting_preattestations state candidate else (* We already triggered preemptive attestation forging, we either have those already or we are waiting for them. *) - inject_early_arrived_attestations new_state + may_inject_early_forged_attestations new_state let quorum_reached_when_waiting_attestations state candidate attestation_qc = let open Lwt_syntax in @@ -916,7 +988,7 @@ let handle_expected_applied_proposal (state : Baking_state.t) = let new_state = update_current_phase new_state Idle in do_nothing new_state -let handle_arriving_attestation state signed_attestation = +let handle_forged_attestation state signed_attestation = let open Lwt_syntax in let { vote_consensus_content = @@ -960,12 +1032,12 @@ let handle_arriving_attestation state signed_attestation = let new_state = {state with round_state = new_round_state} in do_nothing new_state | Idle | Awaiting_attestations | Awaiting_application -> - (* For these three phases, we have necessarily already reached the - prequorum: we are safe to inject. *) - let signed_attestations = - make_singleton_consensus_vote_batch signed_attestation - in - Lwt.return (state, Inject_attestations {signed_attestations}) + (* For these three phases, we should have already reached the prequorum. + If this is not the case, the attestations will not be injected. *) + may_inject_attestations + state + ~first_signed_attestation:signed_attestation + ~other_signed_attestations:[] let handle_forge_event state forge_event = match forge_event with @@ -977,7 +1049,7 @@ let handle_forge_event state forge_event = | Preattestation_ready signed_preattestation -> Lwt.return (state, Inject_preattestation {signed_preattestation}) | Attestation_ready signed_attestation -> - handle_arriving_attestation state signed_attestation + handle_forged_attestation state signed_attestation (* Hypothesis: - The state is not to be modified outside this module -- GitLab From 349c994695c48a17acc6521ccd2c2c983748fe6b Mon Sep 17 00:00:00 2001 From: Albin Coquereau Date: Thu, 11 Jul 2024 10:30:19 +0200 Subject: [PATCH 2/3] paris/baker: rework preattestation injection --- .../lib_delegate/baking_events.ml | 35 +++++++++++++ .../lib_delegate/state_transitions.ml | 50 ++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/proto_020_PsParisC/lib_delegate/baking_events.ml b/src/proto_020_PsParisC/lib_delegate/baking_events.ml index 36c0b3a8c9c2..e66bde793db5 100644 --- a/src/proto_020_PsParisC/lib_delegate/baking_events.ml +++ b/src/proto_020_PsParisC/lib_delegate/baking_events.ml @@ -365,6 +365,21 @@ module State_transitions = struct ~pp2:Baking_state.pp_event ("event", Baking_state.event_encoding) + let discarding_preattestation = + declare_3 + ~section + ~name:"discarding_preattestation" + ~level:Info + ~msg: + "discarding outdated preattestation for {delegate} at level {level}, \ + round {round}" + ~pp1:Baking_state.pp_consensus_key_and_delegate + ("delegate", Baking_state.consensus_key_and_delegate_encoding) + ~pp2:pp_int32 + ("level", Data_encoding.int32) + ~pp3:Round.pp + ("round", Round.encoding) + let discarding_attestation = declare_3 ~section @@ -380,6 +395,26 @@ module State_transitions = struct ~pp3:Round.pp ("round", Round.encoding) + let discarding_unexpected_preattestation_with_different_payload = + declare_5 + ~section + ~name:"discarding_unexpected_preattestation_with_different_payload" + ~level:Warning + ~msg: + "discarding preattestation for {delegate} with payload {payload} at \ + level {level}, round {round} where the prequorum was locked on a \ + different payload {state_payload}." + ~pp1:Baking_state.pp_consensus_key_and_delegate + ("delegate", Baking_state.consensus_key_and_delegate_encoding) + ~pp2:Block_payload_hash.pp + ("payload", Block_payload_hash.encoding) + ~pp3:pp_int32 + ("level", Data_encoding.int32) + ~pp4:Round.pp + ("round", Round.encoding) + ~pp5:Block_payload_hash.pp + ("state_payload", Block_payload_hash.encoding) + let discarding_unexpected_attestation_without_prequorum_payload = declare_3 ~section diff --git a/src/proto_020_PsParisC/lib_delegate/state_transitions.ml b/src/proto_020_PsParisC/lib_delegate/state_transitions.ml index 4e0a3a29cfd2..7b325f9633c8 100644 --- a/src/proto_020_PsParisC/lib_delegate/state_transitions.ml +++ b/src/proto_020_PsParisC/lib_delegate/state_transitions.ml @@ -988,6 +988,54 @@ let handle_expected_applied_proposal (state : Baking_state.t) = let new_state = update_current_phase new_state Idle in do_nothing new_state +let handle_forged_preattestation state signed_preattestation = + let open Lwt_syntax in + let { + vote_consensus_content = + { + level; + round = att_round; + block_payload_hash = att_payload_hash; + slot = _; + }; + delegate; + _; + } = + signed_preattestation.unsigned_consensus_vote + in + let att_level = Raw_level.to_int32 level in + let check_payload state preattestation do_action = + match state.level_state.attestable_payload with + | None -> + (* No attestable payload set, we are free to inject the + preattestations *) + do_action + | Some payload -> + if + not + Block_payload_hash.( + payload.proposal.block.payload_hash + = preattestation.unsigned_consensus_vote.vote_consensus_content + .block_payload_hash) + then + (* The preattestation payload does not match the one set in the + state, we cannot inject the preattestation. *) + let* () = + Events.( + emit + discarding_unexpected_preattestation_with_different_payload + ( delegate, + att_payload_hash, + att_level, + att_round, + payload.proposal.block.payload_hash )) + in + do_nothing state + else do_action + in + check_payload state signed_preattestation + @@ Lwt.return (state, Inject_preattestation {signed_preattestation}) + let handle_forged_attestation state signed_attestation = let open Lwt_syntax in let { @@ -1047,7 +1095,7 @@ let handle_forge_event state forge_event = Inject_block {prepared_block; force_injection = false; asynchronous = true} ) | Preattestation_ready signed_preattestation -> - Lwt.return (state, Inject_preattestation {signed_preattestation}) + handle_forged_preattestation state signed_preattestation | Attestation_ready signed_attestation -> handle_forged_attestation state signed_attestation -- GitLab From 4249d67b63f5875d5b530ea2ed616ef216cb4bfa Mon Sep 17 00:00:00 2001 From: Albin Coquereau Date: Thu, 11 Jul 2024 10:31:01 +0200 Subject: [PATCH 3/3] paris/baker: add events for unexpected (pre)quorum reception --- .../lib_delegate/baking_events.ml | 30 +++++++++++++++++-- .../lib_delegate/state_transitions.ml | 22 ++++++++++---- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/proto_020_PsParisC/lib_delegate/baking_events.ml b/src/proto_020_PsParisC/lib_delegate/baking_events.ml index e66bde793db5..a1075d500dc5 100644 --- a/src/proto_020_PsParisC/lib_delegate/baking_events.ml +++ b/src/proto_020_PsParisC/lib_delegate/baking_events.ml @@ -437,8 +437,8 @@ module State_transitions = struct ~level:Warning ~msg: "discarding attestation for {delegate} with payload {payload} at level \ - {level}, round {round} where the prequorum was on a different \ - payload {state_payload}." + {level}, round {round} where the prequorum was on a different payload \ + {state_payload}." ~pp1:Baking_state.pp_consensus_key_and_delegate ("delegate", Baking_state.consensus_key_and_delegate_encoding) ~pp2:Block_payload_hash.pp @@ -449,6 +449,32 @@ module State_transitions = struct ("round", Round.encoding) ~pp5:Block_payload_hash.pp ("state_payload", Block_payload_hash.encoding) + + let discarding_unexpected_prequorum_reached = + declare_2 + ~section + ~name:"discarding_unexpected_prequorum_reached" + ~level:Info + ~msg: + "discarding unexpected prequorum reached for {candidate} while in \ + {phase} phase." + ~pp1:Block_hash.pp + ("candidate", Block_hash.encoding) + ~pp2:Baking_state.pp_phase + ("phase", Baking_state.phase_encoding) + + let discarding_unexpected_quorum_reached = + declare_2 + ~section + ~name:"discarding_unexpected_quorum_reached" + ~level:Info + ~msg: + "discarding unexpected quorum reached for {candidate} while in {phase} \ + phase." + ~pp1:Block_hash.pp + ("candidate", Block_hash.encoding) + ~pp2:Baking_state.pp_phase + ("phase", Baking_state.phase_encoding) end module Node_rpc = struct diff --git a/src/proto_020_PsParisC/lib_delegate/state_transitions.ml b/src/proto_020_PsParisC/lib_delegate/state_transitions.ml index 7b325f9633c8..9cc23c9604de 100644 --- a/src/proto_020_PsParisC/lib_delegate/state_transitions.ml +++ b/src/proto_020_PsParisC/lib_delegate/state_transitions.ml @@ -1209,10 +1209,20 @@ let step (state : Baking_state.t) (event : Baking_state.event) : preattestation_qc | Awaiting_attestations, Quorum_reached (candidate, attestation_qc) -> quorum_reached_when_waiting_attestations state candidate attestation_qc - (* Unreachable cases *) - | Idle, (Prequorum_reached _ | Quorum_reached _) - | Awaiting_preattestations, Quorum_reached _ - | (Awaiting_application | Awaiting_attestations), Prequorum_reached _ - | Awaiting_application, Quorum_reached _ -> - (* This cannot/should not happen *) + (* Unreachable cases modulo concurrency. *) + | ( (Idle | Awaiting_application | Awaiting_attestations), + Prequorum_reached (candidate, _operations_pqc) ) -> + (* Unexpected prequorum reached, we do not lock on it and discard it. *) + let* () = + Events.( + emit discarding_unexpected_prequorum_reached (candidate.hash, phase)) + in + do_nothing state + | ( (Idle | Awaiting_preattestations | Awaiting_application), + Quorum_reached (candidate, _operations_qc) ) -> + (* Unexpected quorum reached, we discard it. *) + let* () = + Events.( + emit discarding_unexpected_quorum_reached (candidate.hash, phase)) + in do_nothing state -- GitLab