diff --git a/src/lib_data_encoding/data_encoding.ml b/src/lib_data_encoding/data_encoding.ml index 9efa4787e..bf805463b 100644 --- a/src/lib_data_encoding/data_encoding.ml +++ b/src/lib_data_encoding/data_encoding.ml @@ -665,6 +665,7 @@ module Encoding = struct List.for_all (fun (Case { encoding = e }) -> is_obj e) cases | Empty -> true | Ignore -> true + | Mu (_,_,self) -> is_obj (self e) | _ -> false let rec is_tup : type a. a t -> bool = fun e -> @@ -675,6 +676,7 @@ module Encoding = struct | Dynamic_size e -> is_tup e | Union (_,_,cases) -> List.for_all (function Case { encoding = e} -> is_tup e) cases + | Mu (_,_,self) -> is_tup (self e) | _ -> false let merge_objs o1 o2 = diff --git a/src/proto_alpha/lib_protocol/src/alpha_context.mli b/src/proto_alpha/lib_protocol/src/alpha_context.mli index 7186cd2cd..63a565a0f 100644 --- a/src/proto_alpha/lib_protocol/src/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/src/alpha_context.mli @@ -507,7 +507,7 @@ module Delegate : sig val punish: context -> public_key_hash -> Cycle.t -> - context tzresult Lwt.t + (context * Tez.t) tzresult Lwt.t val has_frozen_balance: context -> public_key_hash -> Cycle.t -> @@ -582,6 +582,10 @@ and anonymous_operation = level: Raw_level.t ; nonce: Nonce.t ; } + | Double_endorsement of { + op1: operation ; + op2: operation ; + } | Faucet of { id: Ed25519.Public_key_hash.t ; nonce: MBytes.t ; diff --git a/src/proto_alpha/lib_protocol/src/apply.ml b/src/proto_alpha/lib_protocol/src/apply.ml index 4d68782ee..0a350cadb 100644 --- a/src/proto_alpha/lib_protocol/src/apply.ml +++ b/src/proto_alpha/lib_protocol/src/apply.ml @@ -19,6 +19,12 @@ type error += Too_many_faucet type error += Invalid_endorsement_level type error += Invalid_commitment of { expected: bool } +type error += Invalid_double_endorsement (* `Permanent *) +type error += Inconsistent_double_endorsement of { delegate1: Ed25519.Public_key_hash.t ; delegate2: Ed25519.Public_key_hash.t } (* `Permanent *) +type error += Unrequired_double_endorsement (* `Branch*) +type error += Too_early_double_endorsement of { level: Raw_level.t ; current: Raw_level.t } (* `Temporary *) +type error += Outdated_double_endorsement of { level: Raw_level.t ; last: Raw_level.t } (* `Permanent *) + let () = register_error_kind @@ -107,7 +113,90 @@ let () = Format.fprintf ppf "Unexpected seed's nonce commitment in block header.") Data_encoding.(obj1 (req "expected "bool)) (function Invalid_commitment { expected } -> Some expected | _ -> None) - (fun expected -> Invalid_commitment { expected }) + (fun expected -> Invalid_commitment { expected }) ; + register_error_kind + `Permanent + ~id:"block.invalid_double_endorsement" + ~title:"Invalid double endorsement" + ~description:"A double-endorsement denunciation is malformed" + ~pp:(fun ppf () -> + Format.fprintf ppf "Malformed double-endorsement denunciation") + Data_encoding.empty + (function Invalid_double_endorsement -> Some () | _ -> None) + (fun () -> Invalid_double_endorsement) ; + register_error_kind + `Permanent + ~id:"block.inconsistent_double_endorsement" + ~title:"Inconsistent double endorsement" + ~description:"A double-endorsement denunciation is inconsistent \ + \ (two distinct delegates)" + ~pp:(fun ppf (delegate1, delegate2) -> + Format.fprintf ppf + "Inconsistent double-endorsement denunciation \ + \ (distinct delegate: %a and %a)" + Ed25519.Public_key_hash.pp_short delegate1 + Ed25519.Public_key_hash.pp_short delegate2) + Data_encoding.(obj2 + (req "delegate1" Ed25519.Public_key_hash.encoding) + (req "delegate2" Ed25519.Public_key_hash.encoding)) + (function + | Inconsistent_double_endorsement { delegate1 ; delegate2 } -> + Some (delegate1, delegate2) + | _ -> None) + (fun (delegate1, delegate2) -> + Inconsistent_double_endorsement { delegate1 ; delegate2 }) ; + register_error_kind + `Branch + ~id:"block.unrequired_double_endorsement" + ~title:"Unrequired double endorsement" + ~description:"A double-endorsement denunciation is unrequired" + ~pp:(fun ppf () -> + Format.fprintf ppf "A valid double-endorsement operation cannot \ + \ be applied: the associated delegate \ + \ has previously been denunciated in this cycle.") + Data_encoding.empty + (function Unrequired_double_endorsement -> Some () | _ -> None) + (fun () -> Unrequired_double_endorsement) ; + register_error_kind + `Temporary + ~id:"block.too_early_double_endorsement" + ~title:"Too early double endorsement" + ~description:"A double-endorsement denunciation is in the future" + ~pp:(fun ppf (level, current) -> + Format.fprintf ppf + "A double-endorsement denunciation is in the future \ + \ (current level: %a, endorsement level: %a)" + Raw_level.pp current + Raw_level.pp level) + Data_encoding.(obj2 + (req "level" Raw_level.encoding) + (req "current" Raw_level.encoding)) + (function + | Too_early_double_endorsement { level ; current } -> + Some (level, current) + | _ -> None) + (fun (level, current) -> + Too_early_double_endorsement { level ; current }) ; + register_error_kind + `Permanent + ~id:"block.outdated_double_endorsement" + ~title:"Outdated double endorsement" + ~description:"A double-endorsement denunciation is outdated." + ~pp:(fun ppf (level, last) -> + Format.fprintf ppf + "A double-endorsement denunciation is outdated \ + \ (last acceptable level: %a, endorsement level: %a)" + Raw_level.pp last + Raw_level.pp level) + Data_encoding.(obj2 + (req "level" Raw_level.encoding) + (req "last" Raw_level.encoding)) + (function + | Outdated_double_endorsement { level ; last } -> + Some (level, last) + | _ -> None) + (fun (level, last) -> + Outdated_double_endorsement { level ; last }) let apply_consensus_operation_content ctxt pred_block block_priority operation = function @@ -292,6 +381,46 @@ let apply_anonymous_operation ctxt delegate origination_nonce kind = Nonce.reveal ctxt level nonce >>=? fun ctxt -> return (ctxt, origination_nonce, Tez.zero, Constants.seed_nonce_revelation_tip) + | Double_endorsement { op1 ; op2 } -> begin + match op1.contents, op2.contents with + | Sourced_operations (Consensus_operation (Endorsements e1)), + Sourced_operations (Consensus_operation (Endorsements e2)) + when Raw_level.(e1.level = e2.level) && + not (Block_hash.equal e1.block e2.block) -> + let level = Level.from_raw ctxt e1.level in + let oldest_level = Level.last_allowed_fork_level ctxt in + fail_unless Level.(level < Level.current ctxt) + (Too_early_double_endorsement + { level = level.level ; + current = (Level.current ctxt).level }) >>=? fun () -> + fail_unless Raw_level.(oldest_level <= level.level) + (Outdated_double_endorsement + { level = level.level ; + last = oldest_level }) >>=? fun () -> + (* Whenever a delegate might have multiple endorsement slots for + given level, she should not endorse different block with different + slots. Hence, we don't check that [e1.slots] and [e2.slots] + intersect. *) + Baking.check_endorsements_rights ctxt level e1.slots >>=? fun delegate1 -> + Operation.check_signature delegate1 op1 >>=? fun () -> + Baking.check_endorsements_rights ctxt level e2.slots >>=? fun delegate2 -> + Operation.check_signature delegate2 op2 >>=? fun () -> + fail_unless + (Ed25519.Public_key.equal delegate1 delegate2) + (Inconsistent_double_endorsement + { delegate1 = Ed25519.Public_key.hash delegate1 ; + delegate2 = Ed25519.Public_key.hash delegate2 }) >>=? fun () -> + let delegate = Ed25519.Public_key.hash delegate1 in + Delegate.has_frozen_balance ctxt delegate level.cycle >>=? fun valid -> + fail_unless valid Unrequired_double_endorsement >>=? fun () -> + Delegate.punish ctxt delegate level.cycle >>=? fun (ctxt, burned) -> + let reward = + match Tez.(burned /? 2L) with + | Ok v -> v + | Error _ -> Tez.zero in + return (ctxt, origination_nonce, Tez.zero, reward) + | _, _ -> fail Invalid_double_endorsement + end | Faucet { id = manager ; _ } -> (* Free tez for all! *) if Compare.Int.(faucet_count ctxt < 5) then diff --git a/src/proto_alpha/lib_protocol/src/delegate_storage.ml b/src/proto_alpha/lib_protocol/src/delegate_storage.ml index 05e870273..6fe8bc01e 100644 --- a/src/proto_alpha/lib_protocol/src/delegate_storage.ml +++ b/src/proto_alpha/lib_protocol/src/delegate_storage.ml @@ -261,7 +261,8 @@ let punish ctxt delegate cycle = Storage.Contract.Frozen_bonds.remove (ctxt, contract) cycle >>= fun ctxt -> Storage.Contract.Frozen_fees.remove (ctxt, contract) cycle >>= fun ctxt -> Storage.Contract.Frozen_rewards.remove (ctxt, contract) cycle >>= fun ctxt -> - return ctxt + Lwt.return Tez_repr.(bond +? fees) >>=? fun burned -> + return (ctxt, burned) let has_frozen_balance ctxt delegate cycle = diff --git a/src/proto_alpha/lib_protocol/src/delegate_storage.mli b/src/proto_alpha/lib_protocol/src/delegate_storage.mli index f40287d8d..65971796f 100644 --- a/src/proto_alpha/lib_protocol/src/delegate_storage.mli +++ b/src/proto_alpha/lib_protocol/src/delegate_storage.mli @@ -73,10 +73,10 @@ val cycle_end: Raw_context.t tzresult Lwt.t (** Burn all then frozen bond/fees/rewards for a delegate at a given - cycle. *) + cycle. Returns the burned amount. *) val punish: Raw_context.t -> Ed25519.Public_key_hash.t -> Cycle_repr.t -> - Raw_context.t tzresult Lwt.t + (Raw_context.t * Tez_repr.t) tzresult Lwt.t (** Has the given key some frozen tokens in its implicit contract? *) val has_frozen_balance: diff --git a/src/proto_alpha/lib_protocol/src/operation_repr.ml b/src/proto_alpha/lib_protocol/src/operation_repr.ml index f22ad3895..7f4234f9f 100644 --- a/src/proto_alpha/lib_protocol/src/operation_repr.ml +++ b/src/proto_alpha/lib_protocol/src/operation_repr.ml @@ -31,6 +31,10 @@ and anonymous_operation = level: Raw_level_repr.t ; nonce: Seed_repr.nonce ; } + | Double_endorsement of { + op1: operation ; + op2: operation ; + } | Faucet of { id: Ed25519.Public_key_hash.t ; nonce: MBytes.t ; @@ -306,6 +310,20 @@ module Encoding = struct ) (fun ((), level, nonce) -> Seed_nonce_revelation { level ; nonce }) + let double_endorsement_encoding op_encoding = + (obj3 + (req "kind" (constant "double_endorsement")) + (req "op1" (dynamic_size op_encoding)) + (req "op2" (dynamic_size op_encoding))) + + let double_endorsement_case tag op_encoding = + case tag (double_endorsement_encoding op_encoding) + (function + | Double_endorsement { op1 ; op2 } -> Some ((), op1, op2) + | _ -> None + ) + (fun ((), op1, op2) -> Double_endorsement { op1 ; op2 }) + let faucet_encoding = (obj3 (req "kind" (constant "faucet")) @@ -320,48 +338,58 @@ module Encoding = struct ) (fun ((), id, nonce) -> Faucet { id ; nonce }) - let unsigned_operation_case tag = + let unsigned_operation_case tag op_encoding = case tag (obj1 (req "operations" (list (union [ seed_nonce_revelation_case (Tag 0) ; - faucet_case (Tag 1) ; + double_endorsement_case (Tag 1) op_encoding ; + faucet_case (Tag 2) ; ])))) (function Anonymous_operations ops -> Some ops | _ -> None) (fun ops -> Anonymous_operations ops) - let proto_operation_encoding = + let mu_proto_operation_encoding op_encoding = union [ signed_operations_case (Tag 0) ; - unsigned_operation_case (Tag 1) ; + unsigned_operation_case (Tag 1) op_encoding ; ] + let mu_signed_proto_operation_encoding op_encoding = + merge_objs + (mu_proto_operation_encoding op_encoding) + (obj1 (varopt "signature" Ed25519.Signature.encoding)) + + let operation_encoding = + mu "operation" + (fun encoding -> + conv + (fun { shell ; contents ; signature } -> + (shell, (contents, signature))) + (fun (shell, (contents, signature)) -> + { shell ; contents ; signature }) + (merge_objs + Operation.shell_header_encoding + (mu_signed_proto_operation_encoding encoding))) + + let proto_operation_encoding = + mu_proto_operation_encoding operation_encoding + + let signed_proto_operation_encoding = + mu_signed_proto_operation_encoding operation_encoding + let unsigned_operation_encoding = merge_objs Operation.shell_header_encoding proto_operation_encoding - let signed_proto_operation_encoding = - merge_objs - proto_operation_encoding - (obj1 (varopt "signature" Ed25519.Signature.encoding)) - end type error += Cannot_parse_operation -let encoding = - let open Data_encoding in - conv - (fun { shell ; contents ; signature } -> - (shell, (contents, signature))) - (fun (shell, (contents, signature)) -> - { shell ; contents ; signature }) - (merge_objs - Operation.shell_header_encoding - Encoding.signed_proto_operation_encoding) +let encoding = Encoding.operation_encoding let () = register_error_kind diff --git a/src/proto_alpha/lib_protocol/src/operation_repr.mli b/src/proto_alpha/lib_protocol/src/operation_repr.mli index b9d95dd75..b30bb30dc 100644 --- a/src/proto_alpha/lib_protocol/src/operation_repr.mli +++ b/src/proto_alpha/lib_protocol/src/operation_repr.mli @@ -31,6 +31,10 @@ and anonymous_operation = level: Raw_level_repr.t ; nonce: Seed_repr.nonce ; } + | Double_endorsement of { + op1: operation ; + op2: operation ; + } | Faucet of { id: Ed25519.Public_key_hash.t ; nonce: MBytes.t ;