From 13cd47e121d5345501cce60eaf8c96e59ca123a9 Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Wed, 26 Mar 2025 14:35:03 +0100 Subject: [PATCH 1/4] baker: filter consensus operations branches --- .../lib_delegate/operation_pool.ml | 20 ++++++++++++++----- .../lib_delegate/operation_pool.mli | 2 ++ .../lib_delegate/state_transitions.ml | 12 +++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/proto_alpha/lib_delegate/operation_pool.ml b/src/proto_alpha/lib_delegate/operation_pool.ml index 8049254510a6..8fb6ccba0121 100644 --- a/src/proto_alpha/lib_delegate/operation_pool.ml +++ b/src/proto_alpha/lib_delegate/operation_pool.ml @@ -198,16 +198,18 @@ type consensus_filter = { level : int32; round : Round.t; payload_hash : Block_payload_hash.t; + branch : Block_hash.t; } (** From a pool of operations [operation_pool], the function filters out the attestations that are different from the [current_level], the [current_round] or the optional [current_block_payload_hash], as well as preattestations. *) -let filter_with_relevant_consensus_ops ~(attestation_filter : consensus_filter) +let filter_with_relevant_consensus_ops ~aggregate_attestation_feature_flag + ~(attestation_filter : consensus_filter) ~(preattestation_filter : consensus_filter option) operation_set = Operation_set.filter - (fun {protocol_data; _} -> + (fun {shell = {branch}; protocol_data} -> match (protocol_data, preattestation_filter) with (* 1a. Remove preattestations. *) | Operation_data {contents = Single (Preattestation _); _}, None -> false @@ -219,11 +221,17 @@ let filter_with_relevant_consensus_ops ~(attestation_filter : consensus_filter) _; }, Some - {level = level'; round = round'; payload_hash = block_payload_hash'} - ) -> + { + level = level'; + round = round'; + payload_hash = block_payload_hash'; + branch = branch'; + } ) -> Compare.Int32.(Raw_level.to_int32 level = level') && Round.(round = round') && Block_payload_hash.(block_payload_hash = block_payload_hash') + && ((not aggregate_attestation_feature_flag) + || Block_hash.(branch = branch')) (* 2. Filter attestations. *) | ( Operation_data { @@ -240,7 +248,9 @@ let filter_with_relevant_consensus_ops ~(attestation_filter : consensus_filter) Compare.Int32.(Raw_level.to_int32 level = attestation_filter.level) && Round.(round = attestation_filter.round) && Block_payload_hash.( - block_payload_hash = attestation_filter.payload_hash) + block_payload_hash = attestation_filter.payload_hash + && ((not aggregate_attestation_feature_flag) + || Block_hash.(branch = attestation_filter.branch))) (* 3. Preserve all non-consensus operations. *) | _ -> true) operation_set diff --git a/src/proto_alpha/lib_delegate/operation_pool.mli b/src/proto_alpha/lib_delegate/operation_pool.mli index a7159f557254..547cedf41bc6 100644 --- a/src/proto_alpha/lib_delegate/operation_pool.mli +++ b/src/proto_alpha/lib_delegate/operation_pool.mli @@ -84,9 +84,11 @@ type consensus_filter = { level : int32; round : Round.t; payload_hash : Block_payload_hash.t; + branch : Block_hash.t; } val filter_with_relevant_consensus_ops : + aggregate_attestation_feature_flag:bool -> attestation_filter:consensus_filter -> preattestation_filter:consensus_filter option -> Operation_set.t -> diff --git a/src/proto_alpha/lib_delegate/state_transitions.ml b/src/proto_alpha/lib_delegate/state_transitions.ml index 7c9291c4d956..ae70c3298bbd 100644 --- a/src/proto_alpha/lib_delegate/state_transitions.ml +++ b/src/proto_alpha/lib_delegate/state_transitions.ml @@ -554,9 +554,14 @@ let prepare_block_to_bake ~attestations ?last_proposal Operation_pool.level = predecessor.shell.level; round = predecessor.round; payload_hash = predecessor.payload_hash; + branch = get_branch_from_proposal state.level_state.latest_proposal; } in + let aggregate_attestation_feature_flag = + state.global_state.constants.parametric.aggregate_attestation + in (Operation_pool.filter_with_relevant_consensus_ops + ~aggregate_attestation_feature_flag ~attestation_filter ~preattestation_filter:None current_mempool.consensus @@ -655,6 +660,7 @@ let propose_block_action state delegate round ~last_proposal = Operation_pool.level = proposal.predecessor.shell.level; round = proposal.predecessor.round; payload_hash = proposal.predecessor.payload_hash; + branch = get_branch_from_proposal state.level_state.latest_proposal; } in let preattestation_filter = @@ -663,10 +669,16 @@ let propose_block_action state delegate round ~last_proposal = Operation_pool.level = prequorum.level; round = prequorum.round; payload_hash = prequorum.block_payload_hash; + branch = + get_branch_from_proposal state.level_state.latest_proposal; } in + let aggregate_attestation_feature_flag = + state.global_state.constants.parametric.aggregate_attestation + in Operation_pool.( filter_with_relevant_consensus_ops + ~aggregate_attestation_feature_flag ~attestation_filter ~preattestation_filter all_consensus_operations -- GitLab From 7e2374ad6199910bcba2b5c1b78960f45fbf9037 Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Wed, 19 Mar 2025 12:57:05 +0100 Subject: [PATCH 2/4] baker: add signature aggregation failure error --- src/proto_alpha/lib_delegate/block_forge.ml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/proto_alpha/lib_delegate/block_forge.ml b/src/proto_alpha/lib_delegate/block_forge.ml index 61985125b2fb..e645211aef18 100644 --- a/src/proto_alpha/lib_delegate/block_forge.ml +++ b/src/proto_alpha/lib_delegate/block_forge.ml @@ -26,6 +26,19 @@ open Protocol open Alpha_context +type error += Signature_aggregation_failure + +let () = + register_error_kind + `Permanent + ~id:"Block_forge.signature_aggregation_failure" + ~title:"Signature aggregation failure" + ~description:"Signature aggregation failed." + ~pp:(fun ppf () -> Format.fprintf ppf "Signature aggregation failed.") + Data_encoding.unit + (function Signature_aggregation_failure -> Some () | _ -> None) + (fun () -> Signature_aggregation_failure) + type unsigned_block = { unsigned_block_header : Block_header.t; operations : Tezos_base.Operation.t list list; -- GitLab From 7e2aec1862e49be414dd32df31550ddc442aad6b Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Wed, 19 Mar 2025 01:43:03 +0100 Subject: [PATCH 3/4] baker: aggregate BLS attestations on fresh proposal --- src/proto_alpha/lib_delegate/block_forge.ml | 111 ++++++++++++++++++-- 1 file changed, 101 insertions(+), 10 deletions(-) diff --git a/src/proto_alpha/lib_delegate/block_forge.ml b/src/proto_alpha/lib_delegate/block_forge.ml index e645211aef18..49f061d7974f 100644 --- a/src/proto_alpha/lib_delegate/block_forge.ml +++ b/src/proto_alpha/lib_delegate/block_forge.ml @@ -409,6 +409,91 @@ let apply_with_context ~chain_id ~faked_protocol_data ~user_activated_upgrades in return (shell_header, operations, manager_operations_infos, payload_hash) +(* [aggregate_attestations attestations] aggregate [attestations] in a + single Attestations_aggregate operation. Each operation in [attestations] is + assumed to be eligible for aggregation, meaning that it : + - must include a BLS signature + - must not include DAL content *) +let aggregate_attestations eligible_attestations = + let open Result_syntax in + let aggregate = + List.fold_left + (fun acc ({shell; protocol_data} : Kind.attestation Operation.t) -> + match (protocol_data.contents, protocol_data.signature) with + | ( Single (Attestation {consensus_content; dal_content = None}), + Some (Bls signature) ) -> ( + let {slot; level; round; block_payload_hash} = consensus_content in + match acc with + | None -> + let consensus_content = {level; round; block_payload_hash} in + Some (shell, consensus_content, [slot], [signature]) + | Some (shell, consensus_content, committee, signatures) -> + let committee = slot :: committee in + let signatures = signature :: signatures in + Some (shell, consensus_content, committee, signatures)) + | _, _ -> assert false) + None + eligible_attestations + in + match aggregate with + | None -> return_none + | Some (shell, consensus_content, committee, signatures) -> ( + (* We disable the subgroup check for better performance, as operations + come from the mempool where it has already been checked. *) + match + Signature.Bls.aggregate_signature_opt ~subgroup_check:false signatures + with + | Some signature -> + let contents = + Single (Attestations_aggregate {consensus_content; committee}) + in + let protocol_data = {contents; signature = Some (Bls signature)} in + return (Some {shell; protocol_data = Operation_data protocol_data}) + | None -> tzfail Signature_aggregation_failure) + +(* [partition_consensus_operations_on_proposal operations] partitions consensus + operations as follows : + - a list of tz4 attestations eligible for aggregation + - a Prioritized_operation_set of remaining operations *) +let partition_consensus_operations_on_proposal consensus_operations = + let open Operation_pool in + Prioritized_operation_set.fold + (fun operation (eligible_attestations, remaining_operations) -> + let {shell; protocol_data = Operation_data protocol_data; _} = + Prioritized_operation.packed operation + in + match (protocol_data.contents, protocol_data.signature) with + | Single (Attestation {dal_content = None; _}), Some (Bls _) -> + let attestation : Kind.attestation Operation.t = + {shell; protocol_data} + in + let remaining_operations = + Prioritized_operation_set.remove operation remaining_operations + in + (attestation :: eligible_attestations, remaining_operations) + | _, _ -> (eligible_attestations, remaining_operations)) + consensus_operations + ([], consensus_operations) + +(* [aggregate_attestations_on_proposal attestations] replaces all eligible + attestations from [attestations] by a single Attestations_aggregate. + Attestations are assumed to target the same shell, level, round and + block_payload_hash. *) +let aggregate_attestations_on_proposal attestations = + let open Result_syntax in + let eligible_attestations, remaining_attestations = + partition_consensus_operations_on_proposal attestations + in + let* aggregate_opt = aggregate_attestations eligible_attestations in + match aggregate_opt with + | Some aggregate -> + let open Operation_pool in + return + @@ Prioritized_operation_set.add + (Prioritized_operation.extern ~priority:1 aggregate) + remaining_attestations + | None -> return remaining_attestations + (* [forge] a new [unsigned_block] in accordance with [simulation_kind] and [simulation_mode] *) let forge (cctxt : #Protocol_client_context.full) ~chain_id @@ -420,18 +505,24 @@ let forge (cctxt : #Protocol_client_context.full) ~chain_id let hard_gas_limit_per_block = constants.Constants.Parametric.hard_gas_limit_per_block in - let simulation_kind = + let* simulation_kind = match simulation_kind with | Filter operation_pool -> - (* We cannot include operations that are not live with respect - to our predecessor otherwise the node would reject the block. *) - let filtered_pool = - retain_live_operations_only - ~live_blocks:pred_live_blocks - operation_pool - in - Filter filtered_pool - | Apply _ as x -> x + if constants.aggregate_attestation then + let*? consensus = + aggregate_attestations_on_proposal operation_pool.consensus + in + return (Filter {operation_pool with consensus}) + else + (* We cannot include operations that are not live with respect + to our predecessor otherwise the node would reject the block. *) + let filtered_pool = + retain_live_operations_only + ~live_blocks:pred_live_blocks + operation_pool + in + return (Filter filtered_pool) + | Apply _ as x -> return x in let* shell_header, operations, manager_operations_infos, payload_hash = match (simulation_mode, simulation_kind) with -- GitLab From 089e5621bbfa2549783bc932a9205b7535aa8fc2 Mon Sep 17 00:00:00 2001 From: Adam Allombert-Goget Date: Wed, 19 Mar 2025 01:43:34 +0100 Subject: [PATCH 4/4] baker: aggregate BLS attestations on reproposals --- src/proto_alpha/lib_delegate/block_forge.ml | 95 ++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_delegate/block_forge.ml b/src/proto_alpha/lib_delegate/block_forge.ml index 49f061d7974f..f8e7292c7b1d 100644 --- a/src/proto_alpha/lib_delegate/block_forge.ml +++ b/src/proto_alpha/lib_delegate/block_forge.ml @@ -475,6 +475,35 @@ let partition_consensus_operations_on_proposal consensus_operations = consensus_operations ([], consensus_operations) +(* [partition_consensus_operations_on_reproposal consensus_operations] partitions + [consensus_operations] as follows : + - an optional Attestations_aggregate + - a list that contains all tz4 attestations eligible for aggregation + - a list containing all other remaining consensus operations *) +let partition_consensus_operations_on_reproposal consensus_operations = + List.fold_left + (fun (aggregate_opt, eligible_attestations, other_operations) operation -> + let {shell; protocol_data = Operation_data protocol_data} = operation in + let {contents; signature} = protocol_data in + match (contents, signature) with + | Single (Attestation {dal_content = None; _}), Some (Bls _) -> + let operation : Kind.attestation Operation.t = + {shell; protocol_data} + in + let eligible_attestations = operation :: eligible_attestations in + (aggregate_opt, eligible_attestations, other_operations) + | ( Single (Attestations_aggregate {consensus_content; committee}), + Some (Bls signature) ) -> + let aggregate_opt = + Some (shell, consensus_content, committee, signature) + in + (aggregate_opt, eligible_attestations, other_operations) + | _ -> + let other_operations = operation :: other_operations in + (aggregate_opt, eligible_attestations, other_operations)) + (None, [], []) + consensus_operations + (* [aggregate_attestations_on_proposal attestations] replaces all eligible attestations from [attestations] by a single Attestations_aggregate. Attestations are assumed to target the same shell, level, round and @@ -494,6 +523,63 @@ let aggregate_attestations_on_proposal attestations = remaining_attestations | None -> return remaining_attestations +module SlotSet : Set.S with type elt = Slot.t = Set.Make (Slot) + +(* [aggregate_attestations_on_reproposal consensus_operations] replaces all + eligible attestations from [consensus_operations] by a single + Attestations_aggregate. Attestations are assumed to target the same shell, + level, round and block_payload_hash. *) +let aggregate_attestations_on_reproposal consensus_operations = + let open Result_syntax in + let aggregate_opt, eligible_attestations, other_operations = + partition_consensus_operations_on_reproposal consensus_operations + in + match (aggregate_opt, eligible_attestations) with + | None, [] -> return other_operations + | None, _ :: _ -> ( + (* The proposal did not contain an aggregate. Since additional eligible + attestations are available, we must aggregate them and include the + result in the reproposal. *) + let* aggregate_opt = aggregate_attestations eligible_attestations in + match aggregate_opt with + | Some attestations_aggregate -> + return (attestations_aggregate :: other_operations) + | None -> return other_operations) + | Some _, [] -> return consensus_operations + | Some (shell, consensus_content, committee, signature), _ :: _ -> ( + (* The proposal already contains an aggregate. + We must incorporate additional attestations *) + (* Build the set of aggregated slots for a logarithmic presence lookup *) + let aggregated_slots = SlotSet.of_list committee in + (* Gather slots and signatures incorporating fresh attestations. *) + let committee, signatures = + List.fold_left + (fun ((slots, signatures) as acc) + ({protocol_data; _} : Kind.attestation operation) -> + match (protocol_data.contents, protocol_data.signature) with + | Single (Attestation {consensus_content; _}), Some (Bls signature) + when not (SlotSet.mem consensus_content.slot aggregated_slots) -> + (consensus_content.slot :: slots, signature :: signatures) + | _ -> acc) + (committee, [signature]) + eligible_attestations + in + (* We disable the subgroup check for better performance, as operations + come from the mempool where it has already been checked. *) + match + Signature.Bls.aggregate_signature_opt ~subgroup_check:false signatures + with + | Some signature -> + let contents = + Single (Attestations_aggregate {consensus_content; committee}) + in + let protocol_data = {contents; signature = Some (Bls signature)} in + let attestations_aggregate = + {shell; protocol_data = Operation_data protocol_data} + in + return (attestations_aggregate :: other_operations) + | None -> tzfail Signature_aggregation_failure) + (* [forge] a new [unsigned_block] in accordance with [simulation_kind] and [simulation_mode] *) let forge (cctxt : #Protocol_client_context.full) ~chain_id @@ -522,7 +608,14 @@ let forge (cctxt : #Protocol_client_context.full) ~chain_id operation_pool in return (Filter filtered_pool) - | Apply _ as x -> return x + | Apply {ordered_pool; payload_hash} -> + if constants.aggregate_attestation then + let*? consensus = + aggregate_attestations_on_reproposal ordered_pool.consensus + in + let ordered_pool = {ordered_pool with consensus} in + return (Apply {ordered_pool; payload_hash}) + else return simulation_kind in let* shell_header, operations, manager_operations_infos, payload_hash = match (simulation_mode, simulation_kind) with -- GitLab