diff --git a/docs/protocols/alpha.rst b/docs/protocols/alpha.rst index 48e3a169edf8b95aef276b8e011393c49801af0f..647d5a36559d99d991487af2ec613cdf1c8ae9c1 100644 --- a/docs/protocols/alpha.rst +++ b/docs/protocols/alpha.rst @@ -181,9 +181,11 @@ Minor Changes consistent by adding errors in some cases (BLS12-381 values, Sapling transactions, and timelocks). (MR :gl:`!10227`) -- A delegate may now be slashed once per double baking event and once - per double (pre)attesting event at every level and round - (previously, only at every level, no matter the round). (MR :gl:`!11826`) +- At every level, a delegate may now be slashed for one double baking + per round, one double attesting per round, and one double + preattesting per round. Previously, it was at most one double baking + for the whole level, and one double operation (either attestion or + preattestion) for the whole level. (MRs :gl:`!11826`, :gl:`!11844`) Internal -------- diff --git a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL index c18a7b164d8173d14f7b238a93c4b9ca27a5f135..355f5509e016f53c66a4f5b3dd320e324ae7812b 100644 --- a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL +++ b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL @@ -181,6 +181,7 @@ "Delegate_sampler", "Delegate_rewards", "Delegate_missed_attestations_storage", + "Already_denounced_storage", "Forbidden_delegates_storage", "Slash_percentage", "Delegate_slashed_deposits_storage", diff --git a/src/proto_alpha/lib_protocol/alpha_context.ml b/src/proto_alpha/lib_protocol/alpha_context.ml index 0070f7fd9456ea46f10f666c33c466052fc30e10..4af0f09dd8a06569e2ec49e3af3525fd457ef25c 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.ml +++ b/src/proto_alpha/lib_protocol/alpha_context.ml @@ -540,6 +540,8 @@ module Delegate = struct let is_forbidden_delegate = Forbidden_delegates_storage.is_forbidden + let already_denounced = Already_denounced_storage.already_denounced + module Consensus_key = Delegate_consensus_key module Rewards = struct diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index ff2d0d4be1cd6c9e76c1dc7926fc42bad117f971..bc310be2552ca7488e409c8f378b9d48648cf5b5 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2196,6 +2196,10 @@ module Misbehaviour : sig type kind = Double_baking | Double_attesting | Double_preattesting type t = {kind : kind; level : Raw_level.t; round : Round.t; slot : Slot.t} + + val kind_encoding : kind Data_encoding.t + + val compare_kind : kind -> kind -> int end (** This module re-exports definitions from {!Delegate_storage}, @@ -2231,11 +2235,14 @@ module Delegate : sig Cycle.t -> (context * Receipt.balance_updates * public_key_hash list) tzresult Lwt.t - val already_denounced_for_double_attesting : - context -> public_key_hash -> Level.t -> Round.t -> bool tzresult Lwt.t - - val already_denounced_for_double_baking : - context -> public_key_hash -> Level.t -> Round.t -> bool tzresult Lwt.t + (** See {!Already_denounced_storage.already_denounced}. *) + val already_denounced : + context -> + public_key_hash -> + Level.t -> + Round.t -> + Misbehaviour.kind -> + bool tzresult Lwt.t type reward_and_burn = {reward : Tez.t; amount_to_burn : Tez.t} diff --git a/src/proto_alpha/lib_protocol/already_denounced_storage.ml b/src/proto_alpha/lib_protocol/already_denounced_storage.ml new file mode 100644 index 0000000000000000000000000000000000000000..1d74a0887a046de16cf54cf811c5a305beb62181 --- /dev/null +++ b/src/proto_alpha/lib_protocol/already_denounced_storage.ml @@ -0,0 +1,56 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. *) +(* Copyright (c) 2021 Nomadic Labs, *) +(* Copyright (c) 2022 G.B. Fefe, *) +(* *) +(*****************************************************************************) + +let already_denounced ctxt delegate (level : Level_repr.t) round kind = + let open Lwt_result_syntax in + let* denounced_opt = + Storage.Already_denounced.find + (ctxt, level.cycle) + ((level.level, round), delegate) + in + match (denounced_opt, (kind : Misbehaviour_repr.kind)) with + | None, _ -> return_false + | Some denounced, Double_preattesting -> + return denounced.for_double_preattesting + | Some denounced, Double_attesting -> return denounced.for_double_attesting + | Some denounced, Double_baking -> return denounced.for_double_baking + +let add_denunciation ctxt delegate (level : Level_repr.t) round kind = + let open Lwt_result_syntax in + let* denounced_opt = + Storage.Already_denounced.find + (ctxt, level.cycle) + ((level.level, round), delegate) + in + let denounced = + Option.value denounced_opt ~default:Storage.default_denounced + in + let already_denounced = + match kind with + | Misbehaviour_repr.Double_baking -> denounced.for_double_baking + | Double_attesting -> denounced.for_double_attesting + | Double_preattesting -> denounced.for_double_preattesting + in + let*! ctxt = + if already_denounced then Lwt.return ctxt + else + Storage.Already_denounced.add + (ctxt, level.cycle) + ((level.level, round), delegate) + (match kind with + | Double_baking -> {denounced with for_double_baking = true} + | Double_attesting -> {denounced with for_double_attesting = true} + | Double_preattesting -> {denounced with for_double_preattesting = true}) + in + return (ctxt, already_denounced) + +let clear_outdated_cycle ctxt ~new_cycle = + match Cycle_repr.(sub new_cycle Constants_repr.max_slashing_period) with + | None -> Lwt.return ctxt + | Some outdated_cycle -> Storage.Already_denounced.clear (ctxt, outdated_cycle) diff --git a/src/proto_alpha/lib_protocol/already_denounced_storage.mli b/src/proto_alpha/lib_protocol/already_denounced_storage.mli new file mode 100644 index 0000000000000000000000000000000000000000..a7adfb5b049dc4f721f537525136c58d25da3b86 --- /dev/null +++ b/src/proto_alpha/lib_protocol/already_denounced_storage.mli @@ -0,0 +1,62 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. *) +(* Copyright (c) 2021 Nomadic Labs, *) +(* Copyright (c) 2022 G.B. Fefe, *) +(* *) +(*****************************************************************************) + +(** This module is responsible for ensuring that a delegate doesn't + get slashed twice for the same offense. To do so, it maintains the + {!Storage.Already_denounced} table, which tracks which + denunciations have already been seen in blocks. + + A denunciation is uniquely characterized by the delegate (the + culprit), the level and round of the duplicate block or + (pre)attestation, and the {!type-Misbehaviour_repr.kind} (double + baking/attesting/preattesting). + + Invariant: {!Storage.Already_denounced} is empty for cycles equal + to [current_cycle - max_slashing_period] or older. Indeed, such + denunciations are no longer allowed (see + [Anonymous.check_denunciation_age] in {!Validate}) so there is no + need to track them anymore. *) + +(** Returns true if the given delegate has already been denounced + for the given misbehaviour kind at the given level and round. *) +val already_denounced : + Raw_context.t -> + Signature.Public_key_hash.t -> + Level_repr.t -> + Round_repr.t -> + Misbehaviour_repr.kind -> + bool tzresult Lwt.t + +(** Records a denunciation in {!Storage.Already_denounced}. + + Returns a pair [(ctxt, already_denounced)], where + [already_denounced] is a boolean indicating whether the + denunciation was already recorded in the storage previously. + + When [already_denounced] is [true], the returned [ctxt] is + actually the unchanged context argument. + + Precondition: the given level should be more recent than + [current_cycle - max_slashing_period] in order to maintain the + invariant on the age of tracked denunciations. Fortunately, this + is already enforced in {!Validate} by + [Anonymous.check_denunciation_age]. *) +val add_denunciation : + Raw_context.t -> + Signature.public_key_hash -> + Level_repr.t -> + Round_repr.t -> + Misbehaviour_repr.kind -> + (Raw_context.t * bool) tzresult Lwt.t + +(** Clear {!Storage.Already_denounced} for the cycle [new_cycle - + max_slashing_period]. Indeed, denunciations on events which + happened during this cycle are no longer allowed anyway. *) +val clear_outdated_cycle : + Raw_context.t -> new_cycle:Cycle_repr.t -> Raw_context.t Lwt.t diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index 29816a09807903ad219d6f75069f4d97c78e1080..632cd55f77202ce8a57c8a41974ae5d84bc77f38 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -2406,29 +2406,24 @@ let punish_double_attestation_or_preattestation (type kind) ctxt ~operation_hash | Single (Attestation _) -> Double_attestation_evidence_result {forbidden_delegate; balance_updates} in - match op1.protocol_data.contents with - | Single (Preattestation e1) - | Single (Attestation {consensus_content = e1; dal_content = _}) -> - let level = Level.from_raw ctxt e1.level in - let* ctxt, consensus_pk1 = - Stake_distribution.slot_owner ctxt level e1.slot - in - let misbehaviour = - { - Misbehaviour.kind = Double_attesting; - level = e1.level; - round = e1.round; - slot = e1.slot; - } - in - punish_delegate - ctxt - ~operation_hash - consensus_pk1.delegate - level - misbehaviour - mk_result - ~payload_producer + let {slot; level = raw_level; round; block_payload_hash = _}, kind = + match op1.protocol_data.contents with + | Single (Preattestation consensus_content) -> + (consensus_content, Misbehaviour.Double_preattesting) + | Single (Attestation {consensus_content; dal_content = _}) -> + (consensus_content, Misbehaviour.Double_attesting) + in + let level = Level.from_raw ctxt raw_level in + let* ctxt, consensus_pk1 = Stake_distribution.slot_owner ctxt level slot in + let misbehaviour = {Misbehaviour.kind; level = raw_level; round; slot} in + punish_delegate + ctxt + ~operation_hash + consensus_pk1.delegate + level + misbehaviour + mk_result + ~payload_producer let punish_double_baking ctxt ~operation_hash (bh1 : Block_header.t) ~payload_producer = diff --git a/src/proto_alpha/lib_protocol/delegate_cycles.ml b/src/proto_alpha/lib_protocol/delegate_cycles.ml index 62665da782ed1a135cab6348f4346c8598289547..4bd8974f7cc2e04e4703b2ee02289002733c5157 100644 --- a/src/proto_alpha/lib_protocol/delegate_cycles.ml +++ b/src/proto_alpha/lib_protocol/delegate_cycles.ml @@ -208,11 +208,7 @@ let cycle_end ctxt last_cycle = Delegate_sampler.select_new_distribution_at_cycle_end ctxt ~new_cycle in let*! ctxt = Delegate_consensus_key.activate ctxt ~new_cycle in - let*! ctxt = - Delegate_slashed_deposits_storage.clear_outdated_already_denounced - ctxt - ~new_cycle - in + let*! ctxt = Already_denounced_storage.clear_outdated_cycle ctxt ~new_cycle in let* ctxt, deactivated_delegates = update_activity ctxt last_cycle in let* ctxt, autostake_balance_updates = match Staking.staking_automation ctxt with diff --git a/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.ml b/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.ml index 3d44b1ccb504b5b8171161d775f4adc80e11330e..a2570709e17c91c5d37105d27e8695a7e6c2b8c4 100644 --- a/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.ml +++ b/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.ml @@ -44,30 +44,6 @@ let update_slashing_storage_for_p ctxt = in Storage.Contract.Slashed_deposits__Oxford.clear ctxt -let already_denounced_for_double_attesting ctxt delegate (level : Level_repr.t) - round = - let open Lwt_result_syntax in - let* denounced_opt = - Storage.Already_denounced.find - (ctxt, level.cycle) - ((level.level, round), delegate) - in - match denounced_opt with - | None -> return_false - | Some denounced -> return denounced.for_double_attesting - -let already_denounced_for_double_baking ctxt delegate (level : Level_repr.t) - round = - let open Lwt_result_syntax in - let* denounced_opt = - Storage.Already_denounced.find - (ctxt, level.cycle) - ((level.level, round), delegate) - in - match denounced_opt with - | None -> return_false - | Some denounced -> return denounced.for_double_baking - type reward_and_burn = {reward : Tez_repr.t; amount_to_burn : Tez_repr.t} type punishing_amounts = { @@ -75,39 +51,16 @@ type punishing_amounts = { unstaked : (Cycle_repr.t * reward_and_burn) list; } -let punish_double_signing ctxt ~operation_hash +let record_denunciation ctxt ~operation_hash (misbehaviour : Misbehaviour_repr.t) delegate (level : Level_repr.t) ~rewarded = let open Lwt_result_syntax in - let* denounced_opt = - Storage.Already_denounced.find - (ctxt, level.cycle) - ((level.level, misbehaviour.round), delegate) - in - let denounced = - Option.value denounced_opt ~default:Storage.default_denounced - in (* Placeholder value *) let* ctxt, slashing_percentage = Slash_percentage.get ctxt ~kind:misbehaviour.kind ~level [delegate] in - let already_denounced, updated_denounced = - let Storage.{for_double_baking; for_double_attesting} = denounced in - match misbehaviour.kind with - | Double_baking -> - (for_double_baking, {denounced with for_double_baking = true}) - | Double_attesting | Double_preattesting -> - (for_double_attesting, {denounced with for_double_attesting = true}) - in - assert (Compare.Bool.(already_denounced = false)) ; let delegate_contract = Contract_repr.Implicit delegate in let current_cycle = (Raw_context.current_level ctxt).cycle in - let*! ctxt = - Storage.Already_denounced.add - (ctxt, level.cycle) - ((level.level, misbehaviour.round), delegate) - updated_denounced - in let* slash_history_opt = Storage.Contract.Slashed_deposits.find ctxt delegate_contract in @@ -138,11 +91,50 @@ let punish_double_signing ctxt ~operation_hash in return ctxt -let clear_outdated_already_denounced ctxt ~new_cycle = - let max_slashable_period = Constants_repr.max_slashing_period in - match Cycle_repr.(sub new_cycle max_slashable_period) with - | None -> Lwt.return ctxt - | Some outdated_cycle -> Storage.Already_denounced.clear (ctxt, outdated_cycle) +let punish_double_signing ctxt ~operation_hash misbehaviour delegate + (level : Level_repr.t) ~rewarded = + let open Lwt_result_syntax in + let* ctxt, was_already_denounced = + Already_denounced_storage.add_denunciation + ctxt + delegate + level + misbehaviour.Misbehaviour_repr.round + misbehaviour.kind + in + if was_already_denounced then + (* This can only happen in the very specific case where a delegate + has crafted at least three attestations (respectively + preattestations) on the same level and round but with three + different slots owned by this delegate. Indeed, this makes it + possible to have two denunciations about the same delegate, + level, round, and kind, but different slots. Such denunciations + are considered identical by {!Already_denounced_storage}, which + is good because the delegate shouldn't get slashed twice on the + same level, round, and kind. However, {!Validate}'s conflict + handler identifies denunciations via their slot rather than + delegate for technical reasons (because the slot is readily + available whereas retrieving the delegate requires a call to + {!Delegate_sampler.slot_owner} which is in Lwt and thus + incompatible with some signatures). Therefore, if these + denunciations (which differ only in their slots) are both + included in the same block, then they will both be successfully + validated, and then [was_already_denounced] will be [true] + during the application of the second one. + + In this unlikely scenario, we simply ignore the redundant + denunciation silently. Returning an error or raising an + exception here would cause the whole block application to fail, + which we don't want. *) + return ctxt + else + record_denunciation + ctxt + ~operation_hash + misbehaviour + delegate + level + ~rewarded (* Misbehaviour Map: orders denunciations for application. See {!Misbehaviour_repr.compare} for the order on misbehaviours: diff --git a/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.mli b/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.mli index ee066bab8623d1132598ac5514595be1fb8f3c39..4c19d8189c7e5e3615253db1073fd354ea222c0b 100644 --- a/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.mli +++ b/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.mli @@ -31,24 +31,6 @@ {!Storage.Pending_denunciations} tables. *) -(** Returns true if the given delegate has already been denounced - for double baking for the given level. *) -val already_denounced_for_double_baking : - Raw_context.t -> - Signature.Public_key_hash.t -> - Level_repr.t -> - Round_repr.t -> - bool tzresult Lwt.t - -(** Returns true if the given delegate has already been denounced - for double preattesting or double attesting for the given level. *) -val already_denounced_for_double_attesting : - Raw_context.t -> - Signature.Public_key_hash.t -> - Level_repr.t -> - Round_repr.t -> - bool tzresult Lwt.t - (** The [reward_and_burn] type embeds amounts involved when slashing a delegate for double attesting or double baking. *) type reward_and_burn = {reward : Tez_repr.t; amount_to_burn : Tez_repr.t} @@ -82,12 +64,6 @@ val punish_double_signing : rewarded:Signature.public_key_hash -> Raw_context.t tzresult Lwt.t -(** Clear the part of {!Storage.Already_denounced} about the cycle - [new_cycle - max_slashable_period]. Indeed, denunciations on - events which happened during this cycle are no longer allowed. *) -val clear_outdated_already_denounced : - Raw_context.t -> new_cycle:Cycle_repr.t -> Raw_context.t Lwt.t - (** Applies pending denunciations in {!Storage.Pending_denunciations} at the end of a cycle. The applicable denunciations are those that point to a misbehavior whose max slashable period is ending. diff --git a/src/proto_alpha/lib_protocol/dune b/src/proto_alpha/lib_protocol/dune index 22248cfa95b78624f08ce97ff4b6b1f0e6fe8d4f..c99c16b5c14085f3b8c70ffd931ccaa94e80758e 100644 --- a/src/proto_alpha/lib_protocol/dune +++ b/src/proto_alpha/lib_protocol/dune @@ -197,6 +197,7 @@ Delegate_sampler Delegate_rewards Delegate_missed_attestations_storage + Already_denounced_storage Forbidden_delegates_storage Slash_percentage Delegate_slashed_deposits_storage @@ -496,6 +497,7 @@ delegate_rewards.ml delegate_rewards.mli delegate_missed_attestations_storage.ml delegate_missed_attestations_storage.mli + already_denounced_storage.ml already_denounced_storage.mli forbidden_delegates_storage.ml forbidden_delegates_storage.mli slash_percentage.ml slash_percentage.mli delegate_slashed_deposits_storage.ml delegate_slashed_deposits_storage.mli @@ -796,6 +798,7 @@ delegate_rewards.ml delegate_rewards.mli delegate_missed_attestations_storage.ml delegate_missed_attestations_storage.mli + already_denounced_storage.ml already_denounced_storage.mli forbidden_delegates_storage.ml forbidden_delegates_storage.mli slash_percentage.ml slash_percentage.mli delegate_slashed_deposits_storage.ml delegate_slashed_deposits_storage.mli @@ -1080,6 +1083,7 @@ delegate_rewards.ml delegate_rewards.mli delegate_missed_attestations_storage.ml delegate_missed_attestations_storage.mli + already_denounced_storage.ml already_denounced_storage.mli forbidden_delegates_storage.ml forbidden_delegates_storage.mli slash_percentage.ml slash_percentage.mli delegate_slashed_deposits_storage.ml delegate_slashed_deposits_storage.mli diff --git a/src/proto_alpha/lib_protocol/init_storage.ml b/src/proto_alpha/lib_protocol/init_storage.ml index f94f34f018e82428be50830482af64f7b1697459..911147c4ee2909e364ac91b3b114830df79bdbbb 100644 --- a/src/proto_alpha/lib_protocol/init_storage.ml +++ b/src/proto_alpha/lib_protocol/init_storage.ml @@ -117,11 +117,16 @@ let migrate_already_denounced_from_Oxford ctxt = (ctxt, cycle) ~order:`Undefined ~init:ctxt - ~f:(fun (level, delegate) denounced ctxt -> + ~f:(fun (level, delegate) {for_double_attesting; for_double_baking} ctxt + -> Storage.Already_denounced.add (ctxt, cycle) ((level, Round_repr.zero), delegate) - denounced) + { + for_double_preattesting = for_double_attesting; + for_double_attesting; + for_double_baking; + }) in Storage.Already_denounced__Oxford.clear (ctxt, cycle) in diff --git a/src/proto_alpha/lib_protocol/misbehaviour_repr.mli b/src/proto_alpha/lib_protocol/misbehaviour_repr.mli index 0ced59700c1b50e880d77fcc17a41b28c93f5636..258262b9ef46d3faa9210ab33f75d494127ee1fc 100644 --- a/src/proto_alpha/lib_protocol/misbehaviour_repr.mli +++ b/src/proto_alpha/lib_protocol/misbehaviour_repr.mli @@ -30,6 +30,8 @@ type t = { slot : Slot_repr.t; } +val kind_encoding : kind Data_encoding.t + val encoding : t Data_encoding.t val compare_kind : kind -> kind -> int diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index f013bd216c5d823679f0b60537ca627c3d844c3f..ed3ae0cc68d0b0cb992b8df6d4bb96d84af9d90b 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -1112,23 +1112,20 @@ module Pending_denunciations = (** Per cycle storage *) -type denounced = {for_double_attesting : bool; for_double_baking : bool} +type denounced__Oxford = {for_double_attesting : bool; for_double_baking : bool} -let default_denounced = - {for_double_attesting = false; for_double_baking = false} - -module Denounced = struct - type t = denounced +type denounced = { + for_double_preattesting : bool; + for_double_attesting : bool; + for_double_baking : bool; +} - let encoding = - let open Data_encoding in - conv - (fun {for_double_attesting; for_double_baking} -> - (for_double_attesting, for_double_baking)) - (fun (for_double_attesting, for_double_baking) -> - {for_double_attesting; for_double_baking}) - (obj2 (req "for_double_attesting" bool) (req "for_double_baking" bool)) -end +let default_denounced = + { + for_double_preattesting = false; + for_double_attesting = false; + for_double_baking = false; + } module Cycle = struct module Indexed_context = @@ -1151,7 +1148,27 @@ module Cycle = struct (Raw_level_repr.Index)) (Make_index (Round_repr.Index))) (Public_key_hash_index)) - (Denounced) + (struct + type t = denounced + + let encoding = + let open Data_encoding in + conv + (fun { + for_double_preattesting; + for_double_attesting; + for_double_baking; + } -> + (for_double_preattesting, for_double_attesting, for_double_baking)) + (fun ( for_double_preattesting, + for_double_attesting, + for_double_baking ) -> + {for_double_preattesting; for_double_attesting; for_double_baking}) + (obj3 + (req "for_double_preattesting" bool) + (req "for_double_attesting" bool) + (req "for_double_baking" bool)) + end) module Already_denounced__Oxford = Make_indexed_data_storage @@ -1160,7 +1177,20 @@ module Cycle = struct let name = ["slashed_deposits"] end)) (Pair (Make_index (Raw_level_repr.Index)) (Public_key_hash_index)) - (Denounced) + (struct + type t = denounced__Oxford + + let encoding = + let open Data_encoding in + conv + (fun ({for_double_attesting; for_double_baking} : denounced__Oxford) -> + (for_double_attesting, for_double_baking)) + (fun (for_double_attesting, for_double_baking) -> + {for_double_attesting; for_double_baking}) + (obj2 + (req "for_double_attesting" bool) + (req "for_double_baking" bool)) + end) module Selected_stake_distribution = Indexed_context.Make_map diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index fcfd823b79531fd7024b824ccad1b423064f12f7..c801340207a635ada2e9eaaa212ff547537b32aa 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -485,9 +485,16 @@ module Pending_denunciations : and type key = Signature.public_key_hash and type value = Denunciations_repr.t +(** Needed for the stitching from Oxford to P. Remove this in Q. *) +type denounced__Oxford = {for_double_attesting : bool; for_double_baking : bool} + (** This type is used to track which denunciations have already been recorded, to avoid slashing multiple times the same event. *) -type denounced = {for_double_attesting : bool; for_double_baking : bool} +type denounced = { + for_double_preattesting : bool; + for_double_attesting : bool; + for_double_baking : bool; +} (** {!denounced} with all fields set to [false]. *) val default_denounced : denounced @@ -505,7 +512,7 @@ module Already_denounced__Oxford : Indexed_data_storage with type t := Raw_context.t * Cycle_repr.t and type key = Raw_level_repr.t * Signature.Public_key_hash.t - and type value = denounced + and type value = denounced__Oxford module Pending_staking_parameters : Indexed_data_storage diff --git a/src/proto_alpha/lib_protocol/test/integration/test_adaptive_issuance_roundtrip.ml b/src/proto_alpha/lib_protocol/test/integration/test_adaptive_issuance_roundtrip.ml index e2a22bf8a44c26f30c1cf0c8ae68cf3678ead7bf..187d901fa4f08c000b8d6d134c08eb66fea27f58 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_adaptive_issuance_roundtrip.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_adaptive_issuance_roundtrip.ml @@ -160,14 +160,9 @@ let default_params = Q.(Int32.to_int edge_of_baking_over_staking_billionth // 1_000_000_000); } -type double_signing_kind = - | Double_baking - | Double_attesting - | Double_preattesting - type double_signing_state = { culprit : Signature.Public_key_hash.t; - kind : double_signing_kind; + kind : Protocol.Misbehaviour_repr.kind; evidence : Context.t -> Protocol.Alpha_context.packed_operation; denounced : bool; level : Int32.t; @@ -1400,11 +1395,7 @@ let check_pending_slashings (block, state) : unit tzresult Lwt.t = else let c2 = Signature.Public_key_hash.compare r1 r2 in if c2 <> 0 then c2 - else - match (m1.kind, m2.kind) with - | Double_baking, Double_attesting -> -1 - | x, y when x = y -> 0 - | _ -> 1 + else Protocol.Misbehaviour_repr.compare_kind m1.kind m2.kind in let denunciations_rpc = List.sort compare_denunciations denunciations_rpc in let denunciations_state = @@ -1504,7 +1495,11 @@ let double_attest_op ?other_bakers ~op ~op_evidence ~kind delegate_name let open Lwt_result_syntax in Log.info ~color:Log_module.event_color - "Double (pre)attesting with %s" + "Double %s with %s" + (match kind with + | Protocol.Misbehaviour_repr.Double_preattesting -> "preattesting" + | Double_attesting -> "attesting" + | Double_baking -> assert false) delegate_name ; let delegate = State.find_account delegate_name state in let* baker, _, _, _ = @@ -1623,12 +1618,6 @@ let update_state_denunciation (block, state) following cycle? *) return (state, denounced) else - let kind = - match kind with - | Double_baking -> Protocol.Misbehaviour_repr.Double_baking - | Double_attesting -> Double_attesting - | Double_preattesting -> Double_attesting - in let misbehaviour = { Protocol.Misbehaviour_repr.kind; diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index b24594a23cc6e011b7fa48859765a445adbfd7cc..ff8a5461dead10f46daabe33aaeb8f521503e3e7 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -142,14 +142,15 @@ module Double_baking_evidence_map = struct list (tup2 (tup2 Raw_level.encoding Round.encoding) elt_encoding)) end -module Double_attesting_evidence_map = struct +module Double_operation_evidence_map = struct include Map.Make (struct - type t = Raw_level.t * Round.t * Slot.t + type t = Raw_level.t * Round.t * Slot.t * Misbehaviour.kind - let compare (l, r, s) (l', r', s') = + let compare (l, r, s, k) (l', r', s', k') = Compare.or_else (Raw_level.compare l l') @@ fun () -> Compare.or_else (Round.compare r r') @@ fun () -> - Compare.or_else (Slot.compare s s') @@ fun () -> 0 + Compare.or_else (Slot.compare s s') @@ fun () -> + Misbehaviour.compare_kind k k' end) let encoding elt_encoding = @@ -159,7 +160,11 @@ module Double_attesting_evidence_map = struct Data_encoding.( list (tup2 - (tup3 Raw_level.encoding Round.encoding Slot.encoding) + (tup4 + Raw_level.encoding + Round.encoding + Slot.encoding + Misbehaviour.kind_encoding) elt_encoding)) end @@ -179,7 +184,7 @@ type anonymous_state = { activation_pkhs_seen : Operation_hash.t Ed25519.Public_key_hash.Map.t; double_baking_evidences_seen : Operation_hash.t Double_baking_evidence_map.t; double_attesting_evidences_seen : - Operation_hash.t Double_attesting_evidence_map.t; + Operation_hash.t Double_operation_evidence_map.t; seed_nonce_levels_seen : Operation_hash.t Raw_level.Map.t; vdf_solution_seen : Operation_hash.t option; } @@ -229,7 +234,7 @@ let anonymous_state_encoding = (Double_baking_evidence_map.encoding Operation_hash.encoding)) (req "double_attesting_evidences_seen" - (Double_attesting_evidence_map.encoding Operation_hash.encoding)) + (Double_operation_evidence_map.encoding Operation_hash.encoding)) (req "seed_nonce_levels_seen" (raw_level_map_encoding Operation_hash.encoding)) @@ -239,7 +244,7 @@ let empty_anonymous_state = { activation_pkhs_seen = Ed25519.Public_key_hash.Map.empty; double_baking_evidences_seen = Double_baking_evidence_map.empty; - double_attesting_evidences_seen = Double_attesting_evidence_map.empty; + double_attesting_evidences_seen = Double_operation_evidence_map.empty; seed_nonce_levels_seen = Raw_level.Map.empty; vdf_solution_seen = None; } @@ -1358,11 +1363,13 @@ module Anonymous = struct in let delegate_pk, delegate = (consensus_key1.consensus_pk, delegate1) in let* already_slashed = - Delegate.already_denounced_for_double_attesting - ctxt - delegate - level - e1.round + let kind = + match denunciation_kind with + | Preattestation -> Misbehaviour.Double_preattesting + | Attestation -> Double_attesting + | Block -> Double_baking + in + Delegate.already_denounced ctxt delegate level e1.round kind in let*? () = error_unless @@ -1391,19 +1398,26 @@ module Anonymous = struct in check_double_attesting_evidence ~consensus_operation:Attestation vi op1 op2 + let double_operation_conflict_key (type kind) + (op1 : kind Kind.consensus Operation.t) = + let {slot; level; round; block_payload_hash = _}, kind = + match op1.protocol_data.contents with + | Single (Preattestation cc) -> (cc, Misbehaviour.Double_preattesting) + | Single (Attestation {consensus_content; dal_content = _}) -> + (consensus_content, Double_attesting) + in + (level, round, slot, kind) + let check_double_attesting_evidence_conflict (type kind) vs oph (op1 : kind Kind.consensus Operation.t) = - match op1.protocol_data.contents with - | Single (Preattestation e1) - | Single (Attestation {consensus_content = e1; dal_content = _}) -> ( - match - Double_attesting_evidence_map.find - (e1.level, e1.round, e1.slot) - vs.anonymous_state.double_attesting_evidences_seen - with - | None -> ok_unit - | Some existing -> - Error (Operation_conflict {existing; new_operation = oph})) + match + Double_operation_evidence_map.find + (double_operation_conflict_key op1) + vs.anonymous_state.double_attesting_evidences_seen + with + | None -> ok_unit + | Some existing -> + Error (Operation_conflict {existing; new_operation = oph}) let check_double_preattestation_evidence_conflict vs oph (operation : Kind.double_preattestation_evidence operation) = @@ -1425,20 +1439,17 @@ module Anonymous = struct let add_double_attesting_evidence (type kind) vs oph (op1 : kind Kind.consensus Operation.t) = - match op1.protocol_data.contents with - | Single (Preattestation e1) - | Single (Attestation {consensus_content = e1; dal_content = _}) -> - let double_attesting_evidences_seen = - Double_attesting_evidence_map.add - (e1.level, e1.round, e1.slot) - oph - vs.anonymous_state.double_attesting_evidences_seen - in - { - vs with - anonymous_state = - {vs.anonymous_state with double_attesting_evidences_seen}; - } + let double_attesting_evidences_seen = + Double_operation_evidence_map.add + (double_operation_conflict_key op1) + oph + vs.anonymous_state.double_attesting_evidences_seen + in + { + vs with + anonymous_state = + {vs.anonymous_state with double_attesting_evidences_seen}; + } let add_double_attestation_evidence vs oph (operation : Kind.double_attestation_evidence operation) = @@ -1456,18 +1467,15 @@ module Anonymous = struct let remove_double_attesting_evidence (type kind) vs (op : kind Kind.consensus Operation.t) = - match op.protocol_data.contents with - | Single (Attestation {consensus_content = e; dal_content = _}) - | Single (Preattestation e) -> - let double_attesting_evidences_seen = - Double_attesting_evidence_map.remove - (e.level, e.round, e.slot) - vs.anonymous_state.double_attesting_evidences_seen - in - let anonymous_state = - {vs.anonymous_state with double_attesting_evidences_seen} - in - {vs with anonymous_state} + let double_attesting_evidences_seen = + Double_operation_evidence_map.remove + (double_operation_conflict_key op) + vs.anonymous_state.double_attesting_evidences_seen + in + let anonymous_state = + {vs.anonymous_state with double_attesting_evidences_seen} + in + {vs with anonymous_state} let remove_double_preattestation_evidence vs (operation : Kind.double_preattestation_evidence operation) = @@ -1528,7 +1536,7 @@ module Anonymous = struct in let delegate_pk, delegate = (consensus_key1.consensus_pk, delegate1) in let* already_slashed = - Delegate.already_denounced_for_double_baking ctxt delegate level round1 + Delegate.already_denounced ctxt delegate level round1 Double_baking in let*? () = error_unless