diff --git a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL index d9fe1c4a64248791d24f418f2a7802c7f67467df..219574dce74e5d6063d0864392b12760440b2c10 100644 --- a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL +++ b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL @@ -165,6 +165,7 @@ "Contract_delegate_storage", "Stake_storage", "Unstaked_frozen_deposits_storage", + "Pending_denunciations_storage", "Unstake_requests_storage", "Staking_pseudotokens_storage", diff --git a/src/proto_alpha/lib_protocol/alpha_context.ml b/src/proto_alpha/lib_protocol/alpha_context.ml index 5d1a46e276f4519094ba6dfd30e6500e98b58912..0070f7fd9456ea46f10f666c33c466052fc30e10 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.ml +++ b/src/proto_alpha/lib_protocol/alpha_context.ml @@ -557,10 +557,11 @@ module Delegate = struct module Shared_stake = Shared_stake module For_RPC = struct - include For_RPC include Delegate_storage.For_RPC include Delegate_missed_attestations_storage.For_RPC - include Delegate_slashed_deposits_storage.For_RPC + include Pending_denunciations_storage.For_RPC + + let pending_denunciations = Pending_denunciations_storage.find end end diff --git a/src/proto_alpha/lib_protocol/delegate_cycles.ml b/src/proto_alpha/lib_protocol/delegate_cycles.ml index 5244a8052c98a2b8417712a597a767a26c8f4344..62665da782ed1a135cab6348f4346c8598289547 100644 --- a/src/proto_alpha/lib_protocol/delegate_cycles.ml +++ b/src/proto_alpha/lib_protocol/delegate_cycles.ml @@ -128,49 +128,57 @@ let adjust_frozen_stakes ctxt ~deactivated_delegates : ~order:`Undefined ~init:(ctxt, []) ~f:(fun delegate (ctxt, balance_updates) -> - let* full_staking_balance = - Stake_storage.get_full_staking_balance ctxt delegate + let*! has_been_denounced = + Pending_denunciations_storage.has_pending_denunciations ctxt delegate in - let own_frozen = - Full_staking_balance_repr.own_frozen full_staking_balance - in - let*? optimal_frozen = - Stake_context.optimal_frozen_wrt_delegated_without_ai - ctxt - full_staking_balance - in - let* deposit_limit = - Delegate_storage.frozen_deposits_limit ctxt delegate - in - let optimal_frozen = - match deposit_limit with - | None -> optimal_frozen - | Some deposit_limit -> Tez_repr.min optimal_frozen deposit_limit - in - let* ctxt, new_balance_updates = - if Tez_repr.(optimal_frozen > own_frozen) then - let*? optimal_to_stake = Tez_repr.(optimal_frozen -? own_frozen) in - Staking.stake - ctxt - ~for_next_cycle_use_only_after_slashing:true - ~amount:(`At_most optimal_to_stake) - ~sender:delegate - ~delegate - else if Tez_repr.(optimal_frozen < own_frozen) then - let*? to_unstake = Tez_repr.(own_frozen -? optimal_frozen) in - Staking.request_unstake - ctxt - ~for_next_cycle_use_only_after_slashing:true - ~sender_contract:Contract_repr.(Implicit delegate) - ~delegate - to_unstake - else - Staking.finalize_unstake + if has_been_denounced then return (ctxt, balance_updates) + (* we don't autostake on behalf of delegates who will be slashed *) + else + let* full_staking_balance = + Stake_storage.get_full_staking_balance ctxt delegate + in + let own_frozen = + Full_staking_balance_repr.own_frozen full_staking_balance + in + let*? optimal_frozen = + Stake_context.optimal_frozen_wrt_delegated_without_ai ctxt - ~for_next_cycle_use_only_after_slashing:true - Contract_repr.(Implicit delegate) - in - return (ctxt, new_balance_updates @ balance_updates)) + full_staking_balance + in + let* deposit_limit = + Delegate_storage.frozen_deposits_limit ctxt delegate + in + let optimal_frozen = + match deposit_limit with + | None -> optimal_frozen + | Some deposit_limit -> Tez_repr.min optimal_frozen deposit_limit + in + let* ctxt, new_balance_updates = + if Tez_repr.(optimal_frozen > own_frozen) then + let*? optimal_to_stake = + Tez_repr.(optimal_frozen -? own_frozen) + in + Staking.stake + ctxt + ~for_next_cycle_use_only_after_slashing:true + ~amount:(`At_most optimal_to_stake) + ~sender:delegate + ~delegate + else if Tez_repr.(optimal_frozen < own_frozen) then + let*? to_unstake = Tez_repr.(own_frozen -? optimal_frozen) in + Staking.request_unstake + ctxt + ~for_next_cycle_use_only_after_slashing:true + ~sender_contract:Contract_repr.(Implicit delegate) + ~delegate + to_unstake + else + Staking.finalize_unstake + ctxt + ~for_next_cycle_use_only_after_slashing:true + Contract_repr.(Implicit delegate) + in + return (ctxt, new_balance_updates @ balance_updates)) in List.fold_left_es (fun (ctxt, balance_updates) delegate -> 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 f14b5231cbbab34eff97b4b6290351cc45576dc1..fe14425c5c0886922918fcfc17329f086ec115ec 100644 --- a/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.ml +++ b/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.ml @@ -114,21 +114,12 @@ let punish_double_signing ctxt ~operation_hash if Percentage.(Compare.(previously_slashed_this_cycle >= p100)) then (* Do not store denunciations that have no effects .*) return ctxt else - let* denunciations_opt = - Storage.Pending_denunciations.find ctxt delegate - in - let denunciations = Option.value denunciations_opt ~default:[] in - let denunciations = - Denunciations_repr.add - operation_hash - rewarded - misbehaviour - denunciations - in - let*! ctxt = - Storage.Pending_denunciations.add ctxt delegate denunciations - in - return ctxt + Pending_denunciations_storage.add_denunciation + ctxt + ~misbehaving_delegate:delegate + operation_hash + ~rewarded_delegate:rewarded + misbehaviour in return ctxt @@ -176,7 +167,7 @@ let apply_and_clear_denunciations ctxt = {reward; amount_to_burn} in let* ctxt, balance_updates, remaining_denunciations = - Storage.Pending_denunciations.fold + Pending_denunciations_storage.fold ctxt ~order:`Undefined ~init:(Ok (ctxt, [], [])) @@ -336,14 +327,14 @@ let apply_and_clear_denunciations ctxt = balance_updates, (delegate, denunciations_to_delay) :: remaining_denunciations )) in - let*! ctxt = Storage.Pending_denunciations.clear ctxt in + let*! ctxt = Pending_denunciations_storage.clear ctxt in let*! ctxt = List.fold_left_s (fun ctxt (delegate, current_cycle_denunciations) -> match current_cycle_denunciations with | [] -> Lwt.return ctxt | _ -> - Storage.Pending_denunciations.add + Pending_denunciations_storage.set_denunciations ctxt delegate current_cycle_denunciations) @@ -351,18 +342,3 @@ let apply_and_clear_denunciations ctxt = remaining_denunciations in return (ctxt, balance_updates) - -module For_RPC = struct - let pending_denunciations ctxt delegate = - let open Lwt_result_syntax in - let+ denunciations = Storage.Pending_denunciations.find ctxt delegate in - Option.value denunciations ~default:[] - - let pending_denunciations_list ctxt = - let open Lwt_syntax in - let* r = Storage.Pending_denunciations.bindings ctxt in - let r = - List.map (fun (x, l) -> List.map (fun y -> (x, y)) l) r |> List.flatten - in - return r -end 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 528d3f624aa5eee0c7d9d6983ececdf22de8feca..9d45a438aaa7bec7ca848965c4325fba73e9562f 100644 --- a/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.mli +++ b/src/proto_alpha/lib_protocol/delegate_slashed_deposits_storage.mli @@ -90,16 +90,3 @@ val clear_outdated_already_denounced : val apply_and_clear_denunciations : Raw_context.t -> (Raw_context.t * Receipt_repr.balance_updates) tzresult Lwt.t - -module For_RPC : sig - (** Returns the pending denunciations for the given delegate. *) - val pending_denunciations : - Raw_context.t -> - Signature.Public_key_hash.t -> - Denunciations_repr.t tzresult Lwt.t - - (** Returns all the pending denunciations. *) - val pending_denunciations_list : - Raw_context.t -> - (Signature.Public_key_hash.t * Denunciations_repr.item) list Lwt.t -end diff --git a/src/proto_alpha/lib_protocol/dune b/src/proto_alpha/lib_protocol/dune index f026fdf0eaca0e1edfabde0ec4e10d8a4617e375..e12415dba87c4ba51a72f82fe37ecef7ec0757d2 100644 --- a/src/proto_alpha/lib_protocol/dune +++ b/src/proto_alpha/lib_protocol/dune @@ -182,6 +182,7 @@ Contract_delegate_storage Stake_storage Unstaked_frozen_deposits_storage + Pending_denunciations_storage Unstake_requests_storage Staking_pseudotokens_storage Contract_storage @@ -478,6 +479,7 @@ contract_delegate_storage.ml contract_delegate_storage.mli stake_storage.ml stake_storage.mli unstaked_frozen_deposits_storage.ml unstaked_frozen_deposits_storage.mli + pending_denunciations_storage.ml pending_denunciations_storage.mli unstake_requests_storage.ml unstake_requests_storage.mli staking_pseudotokens_storage.ml staking_pseudotokens_storage.mli contract_storage.ml contract_storage.mli @@ -776,6 +778,7 @@ contract_delegate_storage.ml contract_delegate_storage.mli stake_storage.ml stake_storage.mli unstaked_frozen_deposits_storage.ml unstaked_frozen_deposits_storage.mli + pending_denunciations_storage.ml pending_denunciations_storage.mli unstake_requests_storage.ml unstake_requests_storage.mli staking_pseudotokens_storage.ml staking_pseudotokens_storage.mli contract_storage.ml contract_storage.mli @@ -1058,6 +1061,7 @@ contract_delegate_storage.ml contract_delegate_storage.mli stake_storage.ml stake_storage.mli unstaked_frozen_deposits_storage.ml unstaked_frozen_deposits_storage.mli + pending_denunciations_storage.ml pending_denunciations_storage.mli unstake_requests_storage.ml unstake_requests_storage.mli staking_pseudotokens_storage.ml staking_pseudotokens_storage.mli contract_storage.ml contract_storage.mli diff --git a/src/proto_alpha/lib_protocol/forbidden_delegates_storage.ml b/src/proto_alpha/lib_protocol/forbidden_delegates_storage.ml index 4dca1b42b820aa6a62d724f82aa42086d29767d5..b45185f2829172d668094088167b99768ad4d7c0 100644 --- a/src/proto_alpha/lib_protocol/forbidden_delegates_storage.ml +++ b/src/proto_alpha/lib_protocol/forbidden_delegates_storage.ml @@ -42,21 +42,14 @@ let set_forbidden_delegates ctxt forbidden_delegates = in return ctxt -let has_pending_denunciations ctxt delegate = - let open Lwt_result_syntax in - let* pending_denunciations = - Storage.Pending_denunciations.find ctxt delegate - in - match pending_denunciations with - | None | Some [] -> return_false - | Some (_ :: _) -> return_true - let should_unforbid ctxt delegate ~selection_for_new_cycle = let open Lwt_result_syntax in (* A delegate who has pending denunciations for which slashing has not been applied yet should stay forbidden, because their frozen deposits are going to decrease by a yet unknown amount. *) - let* has_pending_denunciations = has_pending_denunciations ctxt delegate in + let*! has_pending_denunciations = + Pending_denunciations_storage.has_pending_denunciations ctxt delegate + in if has_pending_denunciations then return_false else (* To get unforbidden, a delegate's current frozen deposits must diff --git a/src/proto_alpha/lib_protocol/init_storage.ml b/src/proto_alpha/lib_protocol/init_storage.ml index a45af41e6d0d146bfe319fe68b1f3c34e06c2773..1418a8e75fa2e6e580193a34eb1701cc21df44f6 100644 --- a/src/proto_alpha/lib_protocol/init_storage.ml +++ b/src/proto_alpha/lib_protocol/init_storage.ml @@ -313,7 +313,7 @@ let prepare_first_block chain_id ctxt ~typecheck_smart_contract 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 = Pending_denunciations_storage.clear ctxt in let*! ctxt = migrate_already_denounced_from_Oxford ctxt in let* ctxt = migrate_staking_balance_and_active_delegates_for_p ctxt in let* ctxt = clean_frozen_deposits_for_p ctxt in diff --git a/src/proto_alpha/lib_protocol/pending_denunciations_storage.ml b/src/proto_alpha/lib_protocol/pending_denunciations_storage.ml new file mode 100644 index 0000000000000000000000000000000000000000..60bd70d862d9fd5c64a245564690b985700568af --- /dev/null +++ b/src/proto_alpha/lib_protocol/pending_denunciations_storage.ml @@ -0,0 +1,47 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Nomadic Labs. *) +(* *) +(*****************************************************************************) + +let find ctxt delegate = + let open Lwt_result_syntax in + let* denunciations_opt = Storage.Pending_denunciations.find ctxt delegate in + return @@ Option.value denunciations_opt ~default:[] + +let add_denunciation ctxt ~misbehaving_delegate operation_hash + ~rewarded_delegate misbehaviour = + let open Lwt_result_syntax in + let* denunciations = find ctxt misbehaving_delegate in + let denunciations = + Denunciations_repr.add + operation_hash + rewarded_delegate + misbehaviour + denunciations + in + let*! ctxt = + Storage.Pending_denunciations.add ctxt misbehaving_delegate denunciations + in + return ctxt + +let set_denunciations ctxt delegate denunciations = + match denunciations with + | [] -> Storage.Pending_denunciations.remove ctxt delegate + | _ -> Storage.Pending_denunciations.add ctxt delegate denunciations + +let has_pending_denunciations ctxt delegate = + (* we rely here on the fact that we never insert an empty list in the table *) + Storage.Pending_denunciations.mem ctxt delegate + +let fold = Storage.Pending_denunciations.fold + +let clear ctxt = Storage.Pending_denunciations.clear ctxt + +module For_RPC = struct + let pending_denunciations_list ctxt = + let open Lwt_syntax in + let+ r = Storage.Pending_denunciations.bindings ctxt in + List.map (fun (x, l) -> List.map (fun y -> (x, y)) l) r |> List.flatten +end diff --git a/src/proto_alpha/lib_protocol/pending_denunciations_storage.mli b/src/proto_alpha/lib_protocol/pending_denunciations_storage.mli new file mode 100644 index 0000000000000000000000000000000000000000..ffb5d4e16306a8598182f8e8ad14d25af696bd67 --- /dev/null +++ b/src/proto_alpha/lib_protocol/pending_denunciations_storage.mli @@ -0,0 +1,67 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Nomadic Labs. *) +(* *) +(*****************************************************************************) +(** This module deals with pending denunciations before they are used to slash + delegates. + + This module is responsible for maintaining the table + {!Storage.Pending_denunciations} + + In particular, it maintains the invariant that no key is pointing to an + empty denunciation list. +*) + +(** Returns the pending denunciations list of the given delegate. + It returns an empty list if none are registered. + *) +val find : + Raw_context.t -> + Signature.public_key_hash -> + Denunciations_repr.item list tzresult Lwt.t + +(** Add a denunciation in the list of the given delegate *) +val add_denunciation : + Raw_context.t -> + misbehaving_delegate:Signature.public_key_hash -> + Operation_hash.t -> + rewarded_delegate:Signature.public_key_hash -> + Misbehaviour_repr.t -> + Raw_context.t tzresult Lwt.t + +(** Set the denunciation list of the given delegate. + Previously set denunciations would be erased. +*) +val set_denunciations : + Raw_context.t -> + Signature.public_key_hash -> + Denunciations_repr.t -> + Raw_context.t Lwt.t + +(** Tells if the given delegate has some pending denunciations *) +val has_pending_denunciations : + Raw_context.t -> Signature.public_key_hash -> bool Lwt.t + +(** See {!Storage.Pending_denunciations.fold} *) +val fold : + Raw_context.t -> + order:[`Sorted | `Undefined] -> + init:'a -> + f: + (Signature.public_key_hash -> + Denunciations_repr.item list -> + 'a -> + 'a Lwt.t) -> + 'a Lwt.t + +(** See {!Storage.Pending_denunciations.clear} *) +val clear : Raw_context.t -> Raw_context.t Lwt.t + +module For_RPC : sig + (** Returns a list of all denunciations paired with the offending delegate pkh. *) + val pending_denunciations_list : + Raw_context.t -> + (Signature.public_key_hash * Denunciations_repr.item) list Lwt.t +end diff --git a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml index ec1e78f7fdf94af2baeff651df968d0f575ceb74..817b5c8d5ea87bffccaebfd5251dc02c87f322c9 100644 --- a/src/proto_alpha/lib_protocol/unstake_requests_storage.ml +++ b/src/proto_alpha/lib_protocol/unstake_requests_storage.ml @@ -192,10 +192,7 @@ module For_RPC = struct and remove them from the slashing events (since they haven't been applied yet). Another solution would be to add the slashing cycle in Storage.Contract.Slashed_deposits, but since it's only used for this specific RPC, let's not. *) - let* denunciations_opt = - Storage.Pending_denunciations.find ctxt delegate - in - let denunciations = Option.value denunciations_opt ~default:[] in + let* denunciations = Pending_denunciations_storage.find ctxt delegate in let not_yet_slashed_pct = if is_last_of_cycle then Percentage.p0 else