diff --git a/docs/protocols/alpha.rst b/docs/protocols/alpha.rst index 7542826e51689450ecc77fc1f5a2ed77762561a7..dbaba70fbae2187c5997c3fa4163ad76defad387 100644 --- a/docs/protocols/alpha.rst +++ b/docs/protocols/alpha.rst @@ -151,5 +151,9 @@ 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`) + Internal -------- diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 261fd96a892821f3de07ff0ab384348b02c9120c..f25a8dd2fe9069270445fd2741758b14d81954ff 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2233,11 +2233,11 @@ module Delegate : sig Cycle.t -> (context * Receipt.balance_updates * public_key_hash list) tzresult Lwt.t - val already_slashed_for_double_attesting : - context -> public_key_hash -> Level.t -> bool tzresult Lwt.t + val already_denounced_for_double_attesting : + context -> public_key_hash -> Level.t -> Round.t -> bool tzresult Lwt.t - val already_slashed_for_double_baking : - context -> public_key_hash -> Level.t -> bool tzresult Lwt.t + val already_denounced_for_double_baking : + context -> public_key_hash -> Level.t -> Round.t -> bool tzresult Lwt.t type reward_and_burn = {reward : Tez.t; amount_to_burn : Tez.t} diff --git a/src/proto_alpha/lib_protocol/delegate_cycles.ml b/src/proto_alpha/lib_protocol/delegate_cycles.ml index a7075bb9f2650898c606ab57ec2f46b834837b97..9547c9fe549e2b26b5ba9cb1f9675ef07916e7b1 100644 --- a/src/proto_alpha/lib_protocol/delegate_cycles.ml +++ b/src/proto_alpha/lib_protocol/delegate_cycles.ml @@ -199,7 +199,7 @@ let cycle_end ctxt last_cycle = in let*! ctxt = Delegate_consensus_key.activate ctxt ~new_cycle in let*! ctxt = - Delegate_slashed_deposits_storage.clear_outdated_slashed_deposits + Delegate_slashed_deposits_storage.clear_outdated_already_denounced ctxt ~new_cycle in 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 25463b37ef52e883ba26a5835fa3a7e4f72eb809..fcac690b50b03e6487ba6005ce34c7d9726138b5 100644 --- a/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.ml +++ b/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.ml @@ -25,23 +25,29 @@ (* *) (*****************************************************************************) -let already_slashed_for_double_attesting ctxt delegate (level : Level_repr.t) = +let already_denounced_for_double_attesting ctxt delegate (level : Level_repr.t) + round = let open Lwt_result_syntax in - let* slashed_opt = - Storage.Slashed_deposits.find (ctxt, level.cycle) (level.level, delegate) + let* denounced_opt = + Storage.Already_denounced.find + (ctxt, level.cycle) + ((level.level, round), delegate) in - match slashed_opt with + match denounced_opt with | None -> return_false - | Some slashed -> return slashed.for_double_attesting + | Some denounced -> return denounced.for_double_attesting -let already_slashed_for_double_baking ctxt delegate (level : Level_repr.t) = +let already_denounced_for_double_baking ctxt delegate (level : Level_repr.t) + round = let open Lwt_result_syntax in - let* slashed_opt = - Storage.Slashed_deposits.find (ctxt, level.cycle) (level.level, delegate) + let* denounced_opt = + Storage.Already_denounced.find + (ctxt, level.cycle) + ((level.level, round), delegate) in - match slashed_opt with + match denounced_opt with | None -> return_false - | Some slashed -> return slashed.for_double_baking + | Some denounced -> return denounced.for_double_baking type reward_and_burn = {reward : Tez_repr.t; amount_to_burn : Tez_repr.t} @@ -54,36 +60,38 @@ let punish_double_signing ctxt ~operation_hash (misbehaviour : Misbehaviour_repr.t) delegate (level : Level_repr.t) ~rewarded = let open Lwt_result_syntax in - let* slashed_opt = - Storage.Slashed_deposits.find (ctxt, level.cycle) (level.level, delegate) + let* denounced_opt = + Storage.Already_denounced.find + (ctxt, level.cycle) + ((level.level, misbehaviour.round), delegate) in - let slashed = - Option.value slashed_opt ~default:Storage.default_slashed_level + let denounced = + Option.value denounced_opt ~default:Storage.default_denounced in - let already_slashed, updated_slashed, slashing_percentage = - let Storage.{for_double_baking; for_double_attesting} = slashed in + let already_denounced, updated_denounced, slashing_percentage = + let Storage.{for_double_baking; for_double_attesting} = denounced in match misbehaviour.kind with | Double_baking -> ( for_double_baking, - {slashed with for_double_baking = true}, + {denounced with for_double_baking = true}, Constants_storage .percentage_of_frozen_deposits_slashed_per_double_baking ctxt ) | Double_attesting -> ( for_double_attesting, - {slashed with for_double_attesting = true}, + {denounced with for_double_attesting = true}, Constants_storage .percentage_of_frozen_deposits_slashed_per_double_attestation ctxt ) in - assert (Compare.Bool.(already_slashed = false)) ; + 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.Slashed_deposits.add + Storage.Already_denounced.add (ctxt, level.cycle) - (level.level, delegate) - updated_slashed + ((level.level, misbehaviour.round), delegate) + updated_denounced in let* slash_history_opt = Storage.Contract.Slashed_deposits.find ctxt delegate_contract @@ -124,11 +132,11 @@ let punish_double_signing ctxt ~operation_hash in return ctxt -let clear_outdated_slashed_deposits ctxt ~new_cycle = +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.Slashed_deposits.clear (ctxt, outdated_cycle) + | Some outdated_cycle -> Storage.Already_denounced.clear (ctxt, outdated_cycle) let apply_and_clear_denunciations ctxt = let open Lwt_result_syntax in 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 93bd3cedb4af2fbc87f622a4b7d4cd41e5611ec2..9d45a438aaa7bec7ca848965c4325fba73e9562f 100644 --- a/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.mli +++ b/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.mli @@ -27,24 +27,26 @@ (** This module maintains the storage related to slashing of delegates for double signing. In particular, it is responsible for maintaining the - {!Storage.Slashed_deposits}, {!Storage.Contract.Slashed_deposits}, and + {!Storage.Already_denounced}, {!Storage.Contract.Slashed_deposits}, and {!Storage.Pending_denunciations} tables. *) -(** Returns true if the given delegate has already been slashed +(** Returns true if the given delegate has already been denounced for double baking for the given level. *) -val already_slashed_for_double_baking : +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 slashed +(** Returns true if the given delegate has already been denounced for double preattesting or double attesting for the given level. *) -val already_slashed_for_double_attesting : +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 @@ -80,7 +82,10 @@ val punish_double_signing : rewarded:Signature.public_key_hash -> Raw_context.t tzresult Lwt.t -val clear_outdated_slashed_deposits : +(** 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 val apply_and_clear_denunciations : diff --git a/src/proto_alpha/lib_protocol/init_storage.ml b/src/proto_alpha/lib_protocol/init_storage.ml index aa7c14e0dfe0e96b958f9eaa54613c5fe8a3e1ed..a63583e0c2307e7d099bbac7e71eb9df88a8f750 100644 --- a/src/proto_alpha/lib_protocol/init_storage.ml +++ b/src/proto_alpha/lib_protocol/init_storage.ml @@ -109,6 +109,31 @@ let patch_script ctxt (address, hash, patched_code) = address ; return ctxt +let migrate_already_denounced_from_Oxford ctxt = + let open Lwt_syntax in + let migrate_cycle ctxt cycle = + let* ctxt = + Storage.Already_denounced__Oxford.fold + (ctxt, cycle) + ~order:`Undefined + ~init:ctxt + ~f:(fun (level, delegate) denounced ctxt -> + Storage.Already_denounced.add + (ctxt, cycle) + ((level, Round_repr.zero), delegate) + denounced) + in + Storage.Already_denounced__Oxford.clear (ctxt, cycle) + in + (* Since the max_slashing_period is 2, denunciations are only + relevant if the misbehaviour happened in either the current cycle + or the previous cycle. *) + let current_cycle = (Level_storage.current ctxt).cycle in + let* ctxt = migrate_cycle ctxt current_cycle in + match Cycle_repr.pred current_cycle with + | None -> return ctxt + | Some previous_cycle -> migrate_cycle ctxt previous_cycle + let prepare_first_block chain_id ctxt ~typecheck_smart_contract ~typecheck_smart_rollup ~level ~timestamp ~predecessor = let open Lwt_result_syntax in @@ -200,12 +225,14 @@ let prepare_first_block chain_id ctxt ~typecheck_smart_contract let* ctxt = Sc_rollup_refutation_storage.migrate_clean_refutation_games ctxt in + (* Adaptive Issuance-related migrations from Oxford to P. *) (* We usually clear the table at the end of the cycle but the migration can happen in the middle of the cycle, so we clear it here. Possible consequence: the slashing history could be inconsistent with the pending denunciations, i.e., there could be unstaked_frozen_deposits that are not slashed whereas unstake_requests are slashed. *) let*! ctxt = Storage.Pending_denunciations.clear ctxt in + let*! ctxt = migrate_already_denounced_from_Oxford ctxt in return (ctxt, []) in let* ctxt = diff --git a/src/proto_alpha/lib_protocol/round_repr.ml b/src/proto_alpha/lib_protocol/round_repr.ml index bc31691ef0ba0caa6ab43996a9985a1673a3498c..124c8e5356542d8a2d63195a27e9bf1240ecbed1 100644 --- a/src/proto_alpha/lib_protocol/round_repr.ml +++ b/src/proto_alpha/lib_protocol/round_repr.ml @@ -495,6 +495,32 @@ let level_offset_of_round round_durations ~round = let* offset = raw_level_offset_of_round round_durations ~round in return (Period_repr.of_seconds_exn offset) +module Index = struct + type t = round + + let path_length = 1 + + let to_path round l = Int32.to_string round :: l + + let of_path = function [s] -> Int32.of_string_opt s | _ -> None + + let rpc_arg = + let construct round = Int32.to_string round in + let destruct str = + Int32.of_string_opt str |> Option.to_result ~none:"Cannot parse round" + in + RPC_arg.make + ~descr:"A round integer" + ~name:"block_round" + ~construct + ~destruct + () + + let encoding = encoding + + let compare = compare +end + module Internals_for_test = struct type round_and_offset_raw = {round : round; offset : Period_repr.t} diff --git a/src/proto_alpha/lib_protocol/round_repr.mli b/src/proto_alpha/lib_protocol/round_repr.mli index 31ea658617fd4fb221e3444829f842f8a88b4882..9fc6e54ac2c1baad59f3d9b46827c3a157705867 100644 --- a/src/proto_alpha/lib_protocol/round_repr.mli +++ b/src/proto_alpha/lib_protocol/round_repr.mli @@ -221,6 +221,8 @@ val round_of_timestamp : timestamp:Time_repr.t -> t tzresult +module Index : Storage_description.INDEX with type t = round + module Internals_for_test : sig type round_and_offset_raw = {round : round; offset : Period_repr.t} diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index 5db19a08d24e09e399b171748e8f9ec8067ce10f..301ea5b28444ee74be8100415712f09bd4eb1e4a 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -1090,13 +1090,13 @@ module Pending_denunciations = (** Per cycle storage *) -type slashed_level = {for_double_attesting : bool; for_double_baking : bool} +type denounced = {for_double_attesting : bool; for_double_baking : bool} -let default_slashed_level = +let default_denounced = {for_double_attesting = false; for_double_baking = false} -module Slashed_level = struct - type t = slashed_level +module Denounced = struct + type t = denounced let encoding = let open Data_encoding in @@ -1117,14 +1117,28 @@ module Cycle = struct end)) (Make_index (Cycle_repr.Index)) - module Slashed_deposits = + module Already_denounced = Make_indexed_data_storage (Make_subcontext (Registered) (Indexed_context.Raw_context) + (struct + let name = ["already_denounced"] + end)) + (Pair + (Pair + (Make_index + (Raw_level_repr.Index)) + (Make_index (Round_repr.Index))) + (Public_key_hash_index)) + (Denounced) + + module Already_denounced__Oxford = + Make_indexed_data_storage + (Make_subcontext (Ghost) (Indexed_context.Raw_context) (struct let name = ["slashed_deposits"] end)) (Pair (Make_index (Raw_level_repr.Index)) (Public_key_hash_index)) - (Slashed_level) + (Denounced) module Selected_stake_distribution = Indexed_context.Make_map @@ -1266,7 +1280,8 @@ module Cycle = struct (Staking_parameters_repr) end -module Slashed_deposits = Cycle.Slashed_deposits +module Already_denounced = Cycle.Already_denounced +module Already_denounced__Oxford = Cycle.Already_denounced__Oxford module Pending_consensus_keys = Cycle.Pending_consensus_keys module Pending_staking_parameters = Cycle.Pending_staking_parameters diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index 4f2565e551488132e45d3af996909c9f749bdfcb..624308c5e63bbe7fcb28ad620d21f0a40d75f31a 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -474,17 +474,27 @@ module Pending_denunciations : and type key = Signature.public_key_hash and type value = Denunciations_repr.t -type slashed_level = {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} -(** [slashed_level] with all fields being [false]. *) -val default_slashed_level : slashed_level +(** {!denounced} with all fields set to [false]. *) +val default_denounced : denounced (** Set used to avoid slashing multiple times the same event *) -module Slashed_deposits : +module Already_denounced : + Indexed_data_storage + with type t := Raw_context.t * Cycle_repr.t + and type key = + (Raw_level_repr.t * Round_repr.t) * Signature.Public_key_hash.t + and type value = denounced + +(** Needed for the stitching from Oxford to P. Remove this in Q. *) +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 = slashed_level + and type value = denounced module Pending_staking_parameters : Indexed_data_storage diff --git a/src/proto_alpha/lib_protocol/validate.ml b/src/proto_alpha/lib_protocol/validate.ml index 96a1fcc2bda568530d961228f0c05118d287135f..d0231f2afc09faad36af363ab595465b4076ed5d 100644 --- a/src/proto_alpha/lib_protocol/validate.ml +++ b/src/proto_alpha/lib_protocol/validate.ml @@ -1464,7 +1464,11 @@ module Anonymous = struct in let delegate_pk, delegate = (consensus_key1.consensus_pk, delegate1) in let* already_slashed = - Delegate.already_slashed_for_double_attesting ctxt delegate level + Delegate.already_denounced_for_double_attesting + ctxt + delegate + level + e1.round in let*? () = error_unless @@ -1630,7 +1634,7 @@ module Anonymous = struct in let delegate_pk, delegate = (consensus_key1.consensus_pk, delegate1) in let* already_slashed = - Delegate.already_slashed_for_double_baking ctxt delegate level + Delegate.already_denounced_for_double_baking ctxt delegate level round1 in let*? () = error_unless