diff --git a/src/proto_alpha/lib_delegate/block_forge.ml b/src/proto_alpha/lib_delegate/block_forge.ml index 602cd549363eceb85c1e32dd51f6c5cdbe8b7e8a..3f569540956374a264a3c5801aaa3ec407bbe000 100644 --- a/src/proto_alpha/lib_delegate/block_forge.ml +++ b/src/proto_alpha/lib_delegate/block_forge.ml @@ -396,6 +396,46 @@ let apply_with_context ~chain_id ~faked_protocol_data ~user_activated_upgrades in return (shell_header, operations, manager_operations_infos, payload_hash) +(* [aggregate_preattestations preattestations] aggregate [preattestations] in a + single Preattestations_aggregate operation. Each operation in + [preattestations] is assumed to be eligible for aggregation, meaning that it + includes a BLS signature. *) +let aggregate_preattestations eligible_preattestations = + let open Result_syntax in + let aggregate = + List.fold_left + (fun acc ({shell; protocol_data} : Kind.preattestation Operation.t) -> + match (protocol_data.contents, protocol_data.signature) with + | Single (Preattestation consensus_content), 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_preattestations + 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 (Preattestations_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 Baking_errors.Signature_aggregation_failure) + (* [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 : @@ -470,30 +510,65 @@ let partition_consensus_operations_on_proposal 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 + - an optional Preattestations_aggregate + - a list of attestations eligible for aggregation + - a list of preattestations 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 -> + (fun ( attestations_aggregate_opt, + preattestations_aggregate_opt, + eligible_attestations, + eligible_preattestations, + other_operations ) + operation -> let {shell; protocol_data = Operation_data protocol_data} = operation in let {contents; signature} = protocol_data in match (contents, signature) with + | Single (Preattestation _), Some (Bls _) -> + let op : Kind.preattestation Operation.t = {shell; protocol_data} in + let eligible_preattestations = op :: eligible_preattestations in + ( attestations_aggregate_opt, + preattestations_aggregate_opt, + eligible_attestations, + eligible_preattestations, + other_operations ) | Single (Attestation {dal_content = None; _}), Some (Bls _) -> - let operation : Kind.attestation Operation.t = - {shell; protocol_data} + let op : Kind.attestation Operation.t = {shell; protocol_data} in + let eligible_attestations = op :: eligible_attestations in + ( attestations_aggregate_opt, + preattestations_aggregate_opt, + eligible_attestations, + eligible_preattestations, + other_operations ) + | ( Single (Preattestations_aggregate {consensus_content; committee}), + Some (Bls signature) ) -> + let preattestations_aggregate_opt = + Some (shell, consensus_content, committee, signature, operation) in - let eligible_attestations = operation :: eligible_attestations in - (aggregate_opt, eligible_attestations, other_operations) + ( attestations_aggregate_opt, + preattestations_aggregate_opt, + eligible_attestations, + eligible_preattestations, + other_operations ) | ( Single (Attestations_aggregate {consensus_content; committee}), Some (Bls signature) ) -> - let aggregate_opt = - Some (shell, consensus_content, committee, signature) + let attestations_aggregate_opt = + Some (shell, consensus_content, committee, signature, operation) in - (aggregate_opt, eligible_attestations, other_operations) + ( attestations_aggregate_opt, + preattestations_aggregate_opt, + eligible_attestations, + eligible_preattestations, + other_operations ) | _ -> let other_operations = operation :: other_operations in - (aggregate_opt, eligible_attestations, other_operations)) - (None, [], []) + ( attestations_aggregate_opt, + preattestations_aggregate_opt, + eligible_attestations, + eligible_preattestations, + other_operations )) + (None, None, [], [], []) consensus_operations (* [aggregate_attestations_on_proposal attestations] replaces all eligible @@ -517,32 +592,68 @@ let aggregate_attestations_on_proposal 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 aggregate_preattestations_on_reproposal aggregate_opt + eligible_preattestations = + let open Result_syntax in + match (aggregate_opt, eligible_preattestations) with + | None, [] -> return_none + | None, _ :: _ -> + (* The proposal did not contain an aggregate. Since additional eligible + preattestations are available, we must aggregate them and include the + result in the reproposal. *) + aggregate_preattestations eligible_preattestations + | Some (_, _, _, _, operation), [] -> return_some operation + | Some (shell, consensus_content, committee, signature, _), _ :: _ -> ( + (* The proposal already contains an aggregate. + We must incorporate additional attestations *) + let aggregated_slots = + (* Build the set of aggregated slots for a logarithmic presence lookup *) + 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.preattestation operation) -> + match (protocol_data.contents, protocol_data.signature) with + | Single (Preattestation consensus_content), Some (Bls signature) + when not (SlotSet.mem consensus_content.slot aggregated_slots) -> + (consensus_content.slot :: slots, signature :: signatures) + | _ -> acc) + (committee, [signature]) + eligible_preattestations + 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 (Preattestations_aggregate {consensus_content; committee}) + in + let protocol_data = {contents; signature = Some (Bls signature)} in + let preattestations_aggregate = + {shell; protocol_data = Operation_data protocol_data} + in + return_some preattestations_aggregate + | None -> tzfail Baking_errors.Signature_aggregation_failure) + +let aggregate_attestations_on_reproposal aggregate_opt eligible_attestations = 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, _ :: _ -> ( + | None, [] -> return_none + | 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), _ :: _ -> ( + aggregate_attestations eligible_attestations + | Some (_, _, _, _, operation), [] -> return_some operation + | 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 = + (* Build the set of aggregated slots for a logarithmic presence lookup *) SlotSet.of_list (Operation.committee_slots committee) in (* Gather slots and signatures incorporating fresh attestations. *) @@ -573,9 +684,42 @@ let aggregate_attestations_on_reproposal consensus_operations = let attestations_aggregate = {shell; protocol_data = Operation_data protocol_data} in - return (attestations_aggregate :: other_operations) + return_some attestations_aggregate | None -> tzfail Baking_errors.Signature_aggregation_failure) +(* [aggregate_consensus_operations_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_consensus_operations_on_reproposal consensus_operations = + let open Result_syntax in + let ( attestations_aggregate_opt, + preattestations_aggregate_opt, + eligible_attestations, + eligible_preattestations, + other_operations ) = + partition_consensus_operations_on_reproposal consensus_operations + in + let* attestations_aggregate_opt = + aggregate_attestations_on_reproposal + attestations_aggregate_opt + eligible_attestations + in + let* preattestations_aggregate_opt = + aggregate_preattestations_on_reproposal + preattestations_aggregate_opt + eligible_preattestations + in + match (attestations_aggregate_opt, preattestations_aggregate_opt) with + | Some attestations_aggregate, Some preattestations_aggregate -> + return + (attestations_aggregate :: preattestations_aggregate :: other_operations) + | Some attestations_aggregate, None -> + return (attestations_aggregate :: other_operations) + | None, Some preattestations_aggregate -> + return (preattestations_aggregate :: other_operations) + | None, None -> return other_operations + (* [forge] a new [unsigned_block] in accordance with [simulation_kind] and [simulation_mode] *) let forge (cctxt : #Protocol_client_context.full) ~chain_id @@ -606,7 +750,7 @@ let forge (cctxt : #Protocol_client_context.full) ~chain_id | Apply {ordered_pool; payload_hash} -> if constants.aggregate_attestation then let*? consensus = - aggregate_attestations_on_reproposal ordered_pool.consensus + aggregate_consensus_operations_on_reproposal ordered_pool.consensus in let ordered_pool = {ordered_pool with consensus} in return (Apply {ordered_pool; payload_hash}) diff --git a/tezt/tests/baker_test.ml b/tezt/tests/baker_test.ml index f6bfdf45af6b64cc631698c125a39c6187332960..698640f0cc5e2c261c915ab5f04b132fe73b0d0f 100644 --- a/tezt/tests/baker_test.ml +++ b/tezt/tests/baker_test.ml @@ -482,10 +482,10 @@ let pp_kind fmt = function | Attestation -> Format.fprintf fmt "attestation" | Preattestation -> Format.fprintf fmt "preattestation" -(** [check_consensus_aux kind ~expected found] fails if the set of delegates in - the consensus operations list [found] differs from the set [expected]. - See [check_consensus_operations]. *) -let check_consensus_aux kind ~expected found = +(* [check_non_aggregated_consensus_operations kind ~expected found] fails if the + set of delegates in the consensus operations list [found] differs from the + set [expected]. See [check_consensus_operations]. *) +let check_non_aggregated_consensus_operations kind ~expected found = match (expected, found) with | None, _ -> () | Some expected, _ -> @@ -512,15 +512,55 @@ let check_consensus_aux kind ~expected found = pp sorted_found +let check_aggregated_consensus_operation kind ~expected found = + match (expected, found) with + | _, _ :: _ :: _ -> Test.fail "Multiple %as_aggregate found" pp_kind kind + | None, _ -> () + | Some _, [] -> Test.fail "No %as_aggregate found" pp_kind kind + | Some expected_committee, [aggregate_json] -> + let expected_committee = + public_key_hashes expected_committee |> List.sort String.compare + in + let contents = + JSON.(aggregate_json |-> "contents" |> as_list |> List.hd) + in + let committee = + JSON.(contents |-> "metadata" |-> "committee" |> as_list) + |> List.map JSON.(fun json -> json |-> "delegate" |> as_string) + |> List.sort String.compare + in + if not (List.equal String.equal committee expected_committee) then + let pp = Format.(pp_print_list ~pp_sep:pp_print_cut pp_print_string) in + Test.fail + "@[Wrong %a commitee@,\ + @[expected:@,\ + %a@]@,\ + @[found:@,\ + %a@]@]" + pp_kind + kind + pp + expected_committee + pp + committee + (** Fetch consensus operations and check that they match the expected contents. Defaults to "head" if no [block] is provided. *) -let check_consensus_operations ?expected_aggregated_committee - ?expected_preattestations ?expected_attestations ?block client = +let check_consensus_operations ?expected_attestations_committee + ?expected_preattestations_committee ?expected_preattestations + ?expected_attestations ?block client = let* consensus_operations = fetch_consensus_operations ?block client in (* Partition the consensus operations list by kind *) - let attestations_aggregates, attestations, preattestations = + let ( attestations_aggregates, + preattestations_aggregates, + attestations, + preattestations ) = List.fold_left - (fun (attestations_aggregates, attestations, preattestations) operation -> + (fun ( attestations_aggregates, + preattestations_aggregates, + attestations, + preattestations ) + operation -> let kind = JSON.( operation |-> "contents" |> as_list |> List.hd |-> "kind" @@ -528,31 +568,53 @@ let check_consensus_operations ?expected_aggregated_committee in match kind with | "attestations_aggregate" -> - (operation :: attestations_aggregates, attestations, preattestations) + ( operation :: attestations_aggregates, + preattestations_aggregates, + attestations, + preattestations ) + | "preattestations_aggregate" -> + ( attestations_aggregates, + operation :: preattestations_aggregates, + attestations, + preattestations ) | "attestation" | "attestation_with_dal" -> - (attestations_aggregates, operation :: attestations, preattestations) + ( attestations_aggregates, + preattestations_aggregates, + operation :: attestations, + preattestations ) | "preattestation" -> - (attestations_aggregates, attestations, operation :: preattestations) + ( attestations_aggregates, + preattestations_aggregates, + attestations, + operation :: preattestations ) | _ -> Test.fail "check_consensus_operations: unexpected operation") - ([], [], []) + ([], [], [], []) consensus_operations in (* Checking attestations_aggregate *) - let* () = - match (expected_aggregated_committee, attestations_aggregates) with - | _, _ :: _ :: _ -> Test.fail "Multiple attestations_aggregate found" - | None, _ -> unit - | Some _, [] -> Test.fail "No attestations_aggregate found" - | Some expected_committee, [attestations_aggregate] -> - return @@ check_aggregate ~expected_committee attestations_aggregate + let () = + check_aggregated_consensus_operation + Attestation + ~expected:expected_attestations_committee + attestations_aggregates + in + (* Checking attestations_aggregate *) + let () = + check_aggregated_consensus_operation + Preattestation + ~expected:expected_preattestations_committee + preattestations_aggregates in (* Checking attestations *) let () = - check_consensus_aux Attestation ~expected:expected_attestations attestations + check_non_aggregated_consensus_operations + Attestation + ~expected:expected_attestations + attestations in (* Checking preattestations *) let () = - check_consensus_aux + check_non_aggregated_consensus_operations Preattestation ~expected:expected_preattestations preattestations @@ -604,7 +666,7 @@ let simple_attestations_aggregation = |> List.map (fun (account : Account.key) -> account.Account.alias) in (* Expected committee that should be found in attestations aggregate *) - let expected_aggregated_committee = [bootstrap1; bootstrap2; bootstrap3] in + let expected_attestations_committee = [bootstrap1; bootstrap2; bootstrap3] in (* Expected attestations that should be found non-aggregated *) let expected_attestations = [bootstrap4; bootstrap5] in (* Testing the "bake for" command *) @@ -614,7 +676,7 @@ let simple_attestations_aggregation = log_step 5 "Check consensus operations" ; let* () = check_consensus_operations - ~expected_aggregated_committee + ~expected_attestations_committee ~expected_attestations client in @@ -630,7 +692,7 @@ let simple_attestations_aggregation = log_step 7 "Check consensus operations" ; let* () = check_consensus_operations - ~expected_aggregated_committee + ~expected_attestations_committee ~expected_attestations client in @@ -641,7 +703,7 @@ let simple_attestations_aggregation = log_step 9 "Check consensus operations" ; let* () = check_consensus_operations - ~expected_aggregated_committee + ~expected_attestations_committee ~expected_attestations client in @@ -867,7 +929,7 @@ let attestations_aggregation_on_reproposal = let* _ = Node.wait_for_level node 6 in let* () = check_consensus_operations - ~expected_aggregated_committee:[bootstrap1] + ~expected_attestations_committee:[bootstrap1] ~expected_attestations:[bootstrap5] client in @@ -902,7 +964,7 @@ let attestations_aggregation_on_reproposal = let* _ = Node.wait_for_branch_switch ~level:6 node in let* () = check_consensus_operations - ~expected_aggregated_committee:[bootstrap1; bootstrap2] + ~expected_attestations_committee:[bootstrap1; bootstrap2] ~expected_attestations:[bootstrap4; bootstrap5] client in @@ -964,9 +1026,10 @@ let attestations_aggregation_on_reproposal = let* _ = Node.wait_for_branch_switch ~level:6 node in let* () = check_consensus_operations - ~expected_aggregated_committee:[bootstrap1; bootstrap2; bootstrap3] + ~expected_attestations_committee:[bootstrap1; bootstrap2; bootstrap3] + ~expected_preattestations_committee:[bootstrap1; bootstrap2] ~expected_attestations:[bootstrap4; bootstrap5; bootstrap6] - ~expected_preattestations:[bootstrap1; bootstrap2; bootstrap4; bootstrap5] + ~expected_preattestations:[bootstrap4; bootstrap5] client in unit