From 2b5ce802251b17b8e9c7eec6925d5f59b6edfc12 Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Wed, 19 Mar 2025 16:18:03 +0100 Subject: [PATCH 1/6] proto: add a field attestations_aggregate_seen in the validation state --- src/proto_alpha/lib_protocol/validate.ml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 8406cebecffa..391a29f3ebbe 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -72,6 +72,7 @@ end) type consensus_state = { preattestations_seen : Operation_hash.t Consensus_conflict_map.t; attestations_seen : Operation_hash.t Consensus_conflict_map.t; + attestations_aggregate_seen : Operation_hash.t option; } let consensus_conflict_map_encoding = @@ -90,18 +91,26 @@ let consensus_state_encoding = let open Data_encoding in def "consensus_state" @@ conv - (fun {preattestations_seen; attestations_seen} -> - (preattestations_seen, attestations_seen)) - (fun (preattestations_seen, attestations_seen) -> - {preattestations_seen; attestations_seen}) - (obj2 + (fun { + preattestations_seen; + attestations_seen; + attestations_aggregate_seen; + } -> + (preattestations_seen, attestations_seen, attestations_aggregate_seen)) + (fun ( preattestations_seen, + attestations_seen, + attestations_aggregate_seen ) -> + {preattestations_seen; attestations_seen; attestations_aggregate_seen}) + (obj3 (req "preattestations_seen" consensus_conflict_map_encoding) - (req "attestations_seen" consensus_conflict_map_encoding)) + (req "attestations_seen" consensus_conflict_map_encoding) + (req "attestations_aggregate_seen" (option Operation_hash.encoding))) let empty_consensus_state = { preattestations_seen = Consensus_conflict_map.empty; attestations_seen = Consensus_conflict_map.empty; + attestations_aggregate_seen = None; } type voting_state = { -- GitLab From 597bf8fdfb41d491ba7db0ae673d950e94c40651 Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Thu, 20 Mar 2025 14:06:30 +0100 Subject: [PATCH 2/6] proto: update consensus conflicts errors to handle aggregates --- src/proto_alpha/lib_protocol/validate_errors.ml | 12 ++++++++++-- src/proto_alpha/lib_protocol/validate_errors.mli | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/proto_alpha/lib_protocol/validate_errors.ml b/src/proto_alpha/lib_protocol/validate_errors.ml index 71f007213c69..0e3a0aea0a73 100644 --- a/src/proto_alpha/lib_protocol/validate_errors.ml +++ b/src/proto_alpha/lib_protocol/validate_errors.ml @@ -70,15 +70,23 @@ module Consensus = struct (** This type is only used in consensus operation errors to make them more informative. *) - type consensus_operation_kind = Preattestation | Attestation + type consensus_operation_kind = + | Preattestation + | Attestation + | Attestations_aggregate let consensus_operation_kind_encoding = Data_encoding.string_enum - [("Preattestation", Preattestation); ("Attestation", Attestation)] + [ + ("Preattestation", Preattestation); + ("Attestation", Attestation); + ("Attestations_aggregate", Attestations_aggregate); + ] let consensus_operation_kind_pp fmt = function | Preattestation -> Format.fprintf fmt "Preattestation" | Attestation -> Format.fprintf fmt "Attestation" + | Attestations_aggregate -> Format.fprintf fmt "Attestations_aggregate" (** Errors for preattestation and attestation. *) type error += diff --git a/src/proto_alpha/lib_protocol/validate_errors.mli b/src/proto_alpha/lib_protocol/validate_errors.mli index 84b68af16edf..2345124329c1 100644 --- a/src/proto_alpha/lib_protocol/validate_errors.mli +++ b/src/proto_alpha/lib_protocol/validate_errors.mli @@ -35,7 +35,10 @@ type operation_conflict = (** Errors that may arise while validating a consensus operation. *) module Consensus : sig - type consensus_operation_kind = Preattestation | Attestation + type consensus_operation_kind = + | Preattestation + | Attestation + | Attestations_aggregate (** Errors for preattestations and attestations. *) type error += -- GitLab From 3c5638ee24162c8174a72ffd2a97b1508cdd1d8f Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Tue, 25 Mar 2025 14:55:28 +0100 Subject: [PATCH 3/6] proto: add helpers for attestations_aggregate conflicts --- src/proto_alpha/lib_protocol/validate.ml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 391a29f3ebbe..10917fdef867 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -956,6 +956,30 @@ module Consensus = struct let operation_state = add_attestation operation_state oph operation in return {info; operation_state; block_state} + let check_attestations_aggregate_conflict vs oph = + match vs.consensus_state.attestations_aggregate_seen with + | None -> ok_unit + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) + + let wrap_attestations_aggregate_conflict = function + | Ok () -> ok_unit + | Error conflict -> + result_error + Validate_errors.Consensus.( + Conflicting_consensus_operation + {kind = Attestations_aggregate; conflict}) + + let add_attestations_aggregate operation_state oph = + { + operation_state with + consensus_state = + { + operation_state.consensus_state with + attestations_aggregate_seen = Some oph; + }; + } + let check_attestation_aggregate_signature info public_keys ({shell; protocol_data = {contents = Single content; signature}} : Kind.attestations_aggregate Operation.t) = -- GitLab From 59e6ed3db0c5b66c7b55d1b406d114ba5f19aacd Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Thu, 20 Mar 2025 14:08:42 +0100 Subject: [PATCH 4/6] proto: valid blocks include at most one attestations_aggregate --- src/proto_alpha/lib_protocol/validate.ml | 54 +++++++++++++++--------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 10917fdef867..8be5b80a5744 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -1016,32 +1016,47 @@ module Consensus = struct in return_unit - (* Check conflicts and register each attestations in the conflict map *) - let handle_attestation_aggregate_conflicts validation_state oph + let handle_attestation_aggregate_conflicts + {info; operation_state; block_state} oph ({shell; protocol_data = {contents = Single content; _}} : Kind.attestations_aggregate operation) = let open Lwt_result_syntax in + (* Check that no other Attestations_aggregate operation was previously + recorded in the operation state *) + let*? () = + check_attestations_aggregate_conflict operation_state oph + |> wrap_attestations_aggregate_conflict + in + (* Record the aggregate in the operation state *) + let operation_state = add_attestations_aggregate operation_state oph in + (* Check for attestations conflicts and register each attestations in the + operation state *) let (Attestations_aggregate {consensus_content; committee}) = content in let {level; round; block_payload_hash} : consensus_aggregate_content = consensus_content in - List.fold_left_es - (fun {info; operation_state; block_state} slot -> - let attestation : Kind.attestation operation = - let consensus_content = {slot; level; round; block_payload_hash} in - let contents = - Single (Attestation {consensus_content; dal_content = None}) + let* operation_state = + List.fold_left_es + (fun operation_state slot -> + let attestation : Kind.attestation operation = + let consensus_content = {slot; level; round; block_payload_hash} in + let contents = + Single (Attestation {consensus_content; dal_content = None}) + in + {shell; protocol_data = {contents; signature = None}} in - {shell; protocol_data = {contents; signature = None}} - in - let*? () = - check_attestation_conflict operation_state oph attestation - |> wrap_attestation_conflict - in - let operation_state = add_attestation operation_state oph attestation in - return {info; operation_state; block_state}) - validation_state - committee + let*? () = + check_attestation_conflict operation_state oph attestation + |> wrap_attestation_conflict + in + let operation_state = + add_attestation operation_state oph attestation + in + return operation_state) + operation_state + committee + in + return {info; operation_state; block_state} let validate_attestations_aggregate ~check_signature info operation_state block_state oph @@ -1103,7 +1118,8 @@ module Consensus = struct check_attestation_aggregate_signature info public_keys op else return_unit in - (* Check conflicts and register each attestation in the conflict map *) + (* Check for conflicts and register the aggregate and its underlying + attestations in the validation state. *) let* validation_state = handle_attestation_aggregate_conflicts {info; operation_state; block_state} -- GitLab From 94df262b456834f3b79e59cadbccca5e77629dcd Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Wed, 19 Mar 2025 21:40:44 +0100 Subject: [PATCH 5/6] proto-unit-test: add a test for multiple aggregate per block forbidden --- .../integration/consensus/test_aggregate.ml | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/integration/consensus/test_aggregate.ml b/src/proto_alpha/lib_protocol/test/integration/consensus/test_aggregate.ml index c93e4bda8196..f3663cebab3e 100644 --- a/src/proto_alpha/lib_protocol/test/integration/consensus/test_aggregate.ml +++ b/src/proto_alpha/lib_protocol/test/integration/consensus/test_aggregate.ml @@ -56,6 +56,10 @@ let non_bls_in_aggregate = function | Validate_errors.Consensus.Non_bls_key_in_aggregate -> true | _ -> false +let conflicting_consensus_operation = function + | Validate_errors.Consensus.Conflicting_consensus_operation _ -> true + | _ -> false + let find_aggregate_result receipt = let result_opt = List.find_map @@ -155,6 +159,13 @@ let find_attester_with_non_bls_key = | (Ed25519 _ | Secp256k1 _ | P256 _), slot :: _ -> Some (attester, slot) | _ -> None) +(* [filter_attesters_with_bls_key attesters] filter attesters with BLS keys. *) +let filter_attesters_with_bls_key = + List.filter_map (fun (attester : RPC.Validators.t) -> + match (attester.consensus_key, attester.slots) with + | Bls _, slot :: _ -> Some (attester, slot) + | _ -> None) + let test_aggregate_feature_flag_enabled () = let open Lwt_result_syntax in let* _genesis, attested_block = @@ -208,14 +219,7 @@ let test_aggregate_attestation_with_multiple_bls_attestations () = in let* attesters = Context.get_attesters (B block) in (* Filter delegates with BLS keys that have at least one slot *) - let* bls_delegates_with_slots = - List.filter_map_es - (fun (attester : RPC.Validators.t) -> - match (attester.consensus_key, attester.slots) with - | Bls _, slot :: _ -> return_some (attester, slot) - | _ -> return_none) - attesters - in + let bls_delegates_with_slots = filter_attesters_with_bls_key attesters in let* attestations = List.map_es (fun (delegate, slot) -> @@ -304,6 +308,25 @@ let test_aggregate_attestation_non_bls_delegate () = let*! res = Block.bake ~operation:aggregate block in Assert.proto_error ~loc:__LOC__ res non_bls_in_aggregate +let test_multiple_aggregations_per_block_forbidden () = + let open Lwt_result_syntax in + let* _genesis, block = + init_genesis_with_some_bls_accounts ~aggregate_attestation:true () + in + let* attesters = Context.get_attesters (B block) in + (* Filter delegates with BLS keys that have at least one slot *) + let bls_delegates_with_slots = filter_attesters_with_bls_key attesters in + (* Craft one aggregate per attester *) + let* aggregates = + List.map_es + (fun ((delegate : RPC.Validators.t), _) -> + Op.attestations_aggregate ~committee:[delegate.consensus_key] block) + bls_delegates_with_slots + in + (* Bake a block containing the multiple aggregates and expect an error *) + let*! res = Block.bake ~operations:aggregates block in + Assert.proto_error ~loc:__LOC__ res conflicting_consensus_operation + let tests = [ Tztest.tztest @@ -330,6 +353,10 @@ let tests = "test_aggregate_attestation_non_bls_delegate" `Quick test_aggregate_attestation_non_bls_delegate; + Tztest.tztest + "test_multiple_aggregations_per_block_forbidden" + `Quick + test_multiple_aggregations_per_block_forbidden; ] let () = -- GitLab From 5d38f54dd87ace2562a9df86782d770dd2c635d5 Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Thu, 20 Mar 2025 14:23:28 +0100 Subject: [PATCH 6/6] proto: fix a typo --- src/proto_alpha/lib_protocol/validate.ml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 8be5b80a5744..336e28593a6b 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -980,7 +980,7 @@ module Consensus = struct }; } - let check_attestation_aggregate_signature info public_keys + let check_attestations_aggregate_signature info public_keys ({shell; protocol_data = {contents = Single content; signature}} : Kind.attestations_aggregate Operation.t) = let open Lwt_result_syntax in @@ -1016,7 +1016,7 @@ module Consensus = struct in return_unit - let handle_attestation_aggregate_conflicts + let handle_attestations_aggregate_conflicts {info; operation_state; block_state} oph ({shell; protocol_data = {contents = Single content; _}} : Kind.attestations_aggregate operation) = @@ -1115,13 +1115,13 @@ module Consensus = struct (* Check signature *) let* () = if check_signature then - check_attestation_aggregate_signature info public_keys op + check_attestations_aggregate_signature info public_keys op else return_unit in (* Check for conflicts and register the aggregate and its underlying attestations in the validation state. *) let* validation_state = - handle_attestation_aggregate_conflicts + handle_attestations_aggregate_conflicts {info; operation_state; block_state} oph op -- GitLab