diff --git a/src/proto_alpha/lib_protocol/src/alpha_context.mli b/src/proto_alpha/lib_protocol/src/alpha_context.mli index 8ddb00668..8ff42b23e 100644 --- a/src/proto_alpha/lib_protocol/src/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/src/alpha_context.mli @@ -928,7 +928,7 @@ module Operation : sig type error += Invalid_signature (* `Permanent *) val check_signature: public_key -> _ operation -> unit tzresult Lwt.t - val raw_check_signature: public_key -> _ operation -> unit tzresult + val check_signature_sync: public_key -> _ operation -> unit tzresult val internal_operation_encoding: packed_internal_operation Data_encoding.t @@ -1059,10 +1059,10 @@ val record_endorsement: context -> Signature.Public_key_hash.t -> context val allowed_endorsements: context -> - (Signature.Public_key.t * int list) Signature.Public_key_hash.Map.t + (Signature.Public_key.t * int list * bool) Signature.Public_key_hash.Map.t val init_endorsements: context -> - (Signature.Public_key.t * int list) Signature.Public_key_hash.Map.t -> + (Signature.Public_key.t * int list * bool) Signature.Public_key_hash.Map.t -> context val reset_internal_nonce: context -> context diff --git a/src/proto_alpha/lib_protocol/src/apply.ml b/src/proto_alpha/lib_protocol/src/apply.ml index b44fd5b9e..9b5d8f5ce 100644 --- a/src/proto_alpha/lib_protocol/src/apply.ml +++ b/src/proto_alpha/lib_protocol/src/apply.ml @@ -13,6 +13,7 @@ open Alpha_context type error += Wrong_voting_period of Voting_period.t * Voting_period.t (* `Temporary *) type error += Wrong_endorsement_predecessor of Block_hash.t * Block_hash.t (* `Temporary *) +type error += Duplicate_endorsement of Signature.Public_key_hash.t (* `Branch *) type error += Invalid_endorsement_level type error += Invalid_commitment of { expected: bool } type error += Internal_operation_replay of packed_internal_operation @@ -70,6 +71,17 @@ let () = (req "provided" Voting_period.encoding)) (function Wrong_voting_period (e, p) -> Some (e, p) | _ -> None) (fun (e, p) -> Wrong_voting_period (e, p)); + register_error_kind + `Branch + ~id:"operation.duplicate_endorsement" + ~title:"Duplicate endorsement" + ~description:"Two endorsements received from same delegate" + ~pp:(fun ppf k -> + Format.fprintf ppf "Duplicate endorsement from delegate %a (possible replay attack)." + Signature.Public_key_hash.pp_short k) + Data_encoding.(obj1 (req "delegate" Signature.Public_key_hash.encoding)) + (function Duplicate_endorsement k -> Some k | _ -> None) + (fun k -> Duplicate_endorsement k); register_error_kind `Temporary ~id:"operation.invalid_endorsement_level" @@ -597,25 +609,31 @@ let apply_contents_list : (context * kind contents_result_list) tzresult Lwt.t = match contents_list with | Single (Endorsement { block ; level }) -> - let current_level = (Level.current ctxt).level in fail_unless (Block_hash.equal block pred_block) (Wrong_endorsement_predecessor (pred_block, block)) >>=? fun () -> + let block = operation.shell.branch in + fail_unless + (Block_hash.equal block pred_block) + (Wrong_endorsement_predecessor (pred_block, block)) >>=? fun () -> + let current_level = (Level.current ctxt).level in fail_unless Raw_level.(succ level = current_level) Invalid_endorsement_level >>=? fun () -> - Baking.check_endorsement_rights ctxt operation >>=? fun (delegate, slots) -> - let ctxt = record_endorsement ctxt delegate in - let gap = List.length slots in - let ctxt = Fitness.increase ~gap ctxt in - Lwt.return - Tez.(Constants.endorsement_security_deposit ctxt *? - Int64.of_int gap) >>=? fun deposit -> - add_deposit ctxt delegate deposit >>=? fun ctxt -> - Global.get_last_block_priority ctxt >>=? fun block_priority -> - Baking.endorsement_reward ctxt ~block_priority gap >>=? fun reward -> - Delegate.freeze_rewards ctxt delegate reward >>=? fun ctxt -> - return (ctxt, Single_result (Endorsement_result (delegate, slots))) + Baking.check_endorsement_rights ctxt operation >>=? fun (delegate, slots, used) -> + if used then fail (Duplicate_endorsement delegate) + else + let ctxt = record_endorsement ctxt delegate in + let gap = List.length slots in + let ctxt = Fitness.increase ~gap ctxt in + Lwt.return + Tez.(Constants.endorsement_security_deposit ctxt *? + Int64.of_int gap) >>=? fun deposit -> + add_deposit ctxt delegate deposit >>=? fun ctxt -> + Global.get_last_block_priority ctxt >>=? fun block_priority -> + Baking.endorsement_reward ctxt ~block_priority gap >>=? fun reward -> + Delegate.freeze_rewards ctxt delegate reward >>=? fun ctxt -> + return (ctxt, Single_result (Endorsement_result (delegate, slots))) | Single (Seed_nonce_revelation { level ; nonce }) -> let level = Level.from_raw ctxt level in Nonce.reveal ctxt level nonce >>=? fun ctxt -> @@ -639,8 +657,8 @@ let apply_contents_list (Outdated_double_endorsement_evidence { level = level.level ; last = oldest_level }) >>=? fun () -> - Baking.check_endorsement_rights ctxt op1 >>=? fun (delegate1, _) -> - Baking.check_endorsement_rights ctxt op2 >>=? fun (delegate2, _) -> + Baking.check_endorsement_rights ctxt op1 >>=? fun (delegate1, _, _) -> + Baking.check_endorsement_rights ctxt op2 >>=? fun (delegate2, _, _) -> fail_unless (Signature.Public_key_hash.equal delegate1 delegate2) (Inconsistent_double_endorsement_evidence diff --git a/src/proto_alpha/lib_protocol/src/apply_operation_result.ml b/src/proto_alpha/lib_protocol/src/apply_operation_result.ml index 55aecb348..e8ac86403 100644 --- a/src/proto_alpha/lib_protocol/src/apply_operation_result.ml +++ b/src/proto_alpha/lib_protocol/src/apply_operation_result.ml @@ -386,7 +386,7 @@ module Encoding = struct encoding = (obj2 (req "delegate" Signature.Public_key_hash.encoding) - (req "slots" (list uint8))) ; + (req "slots" (list uint8))); select = (function | Contents_result (Endorsement_result _ as op) -> Some op diff --git a/src/proto_alpha/lib_protocol/src/baking.ml b/src/proto_alpha/lib_protocol/src/baking.ml index 11123e822..93a012a1e 100644 --- a/src/proto_alpha/lib_protocol/src/baking.ml +++ b/src/proto_alpha/lib_protocol/src/baking.ml @@ -13,7 +13,7 @@ open Misc type error += Invalid_fitness_gap of int64 * int64 (* `Permanent *) type error += Timestamp_too_early of Timestamp.t * Timestamp.t (* `Permanent *) -type error += Unexpected_endorsement +type error += Unexpected_endorsement (* `Permanent *) type error += Invalid_block_signature of Block_hash.t * Signature.Public_key_hash.t (* `Permanent *) type error += Invalid_signature (* `Permanent *) type error += Invalid_stamp (* `Permanent *) @@ -158,11 +158,11 @@ let endorsement_rights c level = (fun acc slot -> Roll.endorsement_rights_owner c level ~slot >>=? fun pk -> let pkh = Signature.Public_key.hash pk in - let slots = + let right = match Signature.Public_key_hash.Map.find_opt pkh acc with - | None -> (pk, [slot]) - | Some (pk, slots) -> (pk, slot :: slots) in - return (Signature.Public_key_hash.Map.add pkh slots acc)) + | None -> (pk, [slot], false) + | Some (pk, slots, used) -> (pk, slot :: slots, used) in + return (Signature.Public_key_hash.Map.add pkh right acc)) Signature.Public_key_hash.Map.empty (0 --> (Constants.endorsers_per_block c - 1)) @@ -174,20 +174,17 @@ let check_endorsement_rights ctxt (op : Kind.endorsement Operation.t) = return (Alpha_context.allowed_endorsements ctxt) else endorsement_rights ctxt (Level.from_raw ctxt level) - end >>=? fun map -> + end >>=? fun endorsements -> match - Signature.Public_key_hash.Map.find_first_opt - (fun pkh -> - let pk, _ = Signature.Public_key_hash.Map.find pkh map in - match Operation.raw_check_signature pk op with - | Error _ -> false - | Ok () -> true) - map + Signature.Public_key_hash.Map.fold (* no find_first *) + (fun pkh (pk, slots, used) acc -> + match Operation.check_signature_sync pk op with + | Error _ -> acc + | Ok () -> Some (pkh, slots, used)) + endorsements None with - | None -> - fail Unexpected_endorsement - | Some (pkh, (_pk, slots)) -> - return (pkh, slots) + | None -> fail Unexpected_endorsement + | Some v -> return v let select_delegate delegate delegate_list max_priority = let rec loop acc l n = diff --git a/src/proto_alpha/lib_protocol/src/baking.mli b/src/proto_alpha/lib_protocol/src/baking.mli index 8c659f0d3..2a7f25510 100644 --- a/src/proto_alpha/lib_protocol/src/baking.mli +++ b/src/proto_alpha/lib_protocol/src/baking.mli @@ -31,18 +31,19 @@ val check_baking_rights: context -> Block_header.contents -> Time.t -> public_key tzresult Lwt.t -(** [check_endorsements_rights c slots]: - * verifies that the endorsement slots are valid ; - * verifies that the endorsement slots correspond to the same delegate at the current level; -*) -val check_endorsement_rights: - context -> Kind.endorsement Operation.t -> - (public_key_hash * int list) tzresult Lwt.t - +(** For a given level computes who has the right to + include an endorsement in the next block. + The result can be stored in Alpha_context.allowed_endorsements *) val endorsement_rights: context -> Level.t -> - (public_key * int list) Signature.Public_key_hash.Map.t tzresult Lwt.t + (public_key * int list * bool) Signature.Public_key_hash.Map.t tzresult Lwt.t + +(** Check that the operation was signed by a delegate allowed + to endorse at the level specified by the endorsement. *) +val check_endorsement_rights: + context -> Kind.endorsement Operation.t -> + (public_key_hash * int list * bool) tzresult Lwt.t (** Returns the endorsement reward calculated w.r.t a given priotiry. *) val endorsement_reward: context -> block_priority:int -> int -> Tez.t tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/src/delegate_services.ml b/src/proto_alpha/lib_protocol/src/delegate_services.ml index 8607151c8..780b92484 100644 --- a/src/proto_alpha/lib_protocol/src/delegate_services.ml +++ b/src/proto_alpha/lib_protocol/src/delegate_services.ml @@ -460,7 +460,7 @@ module Endorsing_rights = struct RPC_service.get_service ~description: "Retrieves the delegates allowed to endorse a block.\n\ - By default, it gives the endorsement slots for bakers that \ + By default, it gives the endorsement slots for delegates that \ have at least one in the next block.\n\ Parameters `level` and `cycle` can be used to specify the \ (valid) level(s) in the past or future at which the \ @@ -483,7 +483,7 @@ module Endorsing_rights = struct Baking.endorsement_rights ctxt level >>=? fun rights -> return (Signature.Public_key_hash.Map.fold - (fun delegate (_, slots) acc -> { + (fun delegate (_, slots, _) acc -> { level = level.level ; delegate ; slots ; estimated_time } :: acc) rights []) @@ -529,4 +529,3 @@ let baking_rights ctxt max_priority = List.map (fun { Baking_rights.delegate ; timestamp ; _ } -> (delegate, timestamp)) l) - diff --git a/src/proto_alpha/lib_protocol/src/operation_repr.ml b/src/proto_alpha/lib_protocol/src/operation_repr.ml index 6de73b3ee..1be4d3142 100644 --- a/src/proto_alpha/lib_protocol/src/operation_repr.ml +++ b/src/proto_alpha/lib_protocol/src/operation_repr.ml @@ -646,7 +646,7 @@ let () = (function Missing_signature -> Some () | _ -> None) (fun () -> Missing_signature) -let raw_check_signature (type kind) key ({ shell ; protocol_data } : kind operation) = +let check_signature_sync (type kind) key ({ shell ; protocol_data } : kind operation) = let check ~watermark contents signature = let unsigned_operation = Data_encoding.Binary.to_bytes_exn @@ -668,7 +668,7 @@ let raw_check_signature (type kind) key ({ shell ; protocol_data } : kind operat check ~watermark:Generic_operation (Contents_list contents) signature let check_signature pk op = - Lwt.return (raw_check_signature pk op) + Lwt.return (check_signature_sync pk op) let hash_raw = Operation.hash let hash (o : _ operation) = diff --git a/src/proto_alpha/lib_protocol/src/operation_repr.mli b/src/proto_alpha/lib_protocol/src/operation_repr.mli index 0755a1e4d..8589afd7c 100644 --- a/src/proto_alpha/lib_protocol/src/operation_repr.mli +++ b/src/proto_alpha/lib_protocol/src/operation_repr.mli @@ -165,7 +165,7 @@ type error += Invalid_signature (* `Permanent *) val check_signature: Signature.Public_key.t -> _ operation -> unit tzresult Lwt.t -val raw_check_signature: +val check_signature_sync: Signature.Public_key.t -> _ operation -> unit tzresult diff --git a/src/proto_alpha/lib_protocol/src/raw_context.ml b/src/proto_alpha/lib_protocol/src/raw_context.ml index 699a24856..a0d7ec771 100644 --- a/src/proto_alpha/lib_protocol/src/raw_context.ml +++ b/src/proto_alpha/lib_protocol/src/raw_context.ml @@ -18,7 +18,7 @@ type t = { fitness: Int64.t ; deposits: Tez_repr.t Signature.Public_key_hash.Map.t ; allowed_endorsements: - (Signature.Public_key.t * int list) Signature.Public_key_hash.Map.t ; + (Signature.Public_key.t * int list * bool) Signature.Public_key_hash.Map.t ; fees: Tez_repr.t ; rewards: Tez_repr.t ; block_gas: Z.t ; @@ -41,11 +41,23 @@ let constants ctxt = ctxt.constants let recover ctxt = ctxt.context let record_endorsement ctxt k = - { ctxt with - allowed_endorsements = - Signature.Public_key_hash.Map.remove k ctxt.allowed_endorsements } + match Signature.Public_key_hash.Map.find_opt k ctxt.allowed_endorsements with + | None -> assert false + | Some (_, _, true) -> assert false (* right already used *) + | Some (d, s, false) -> + { ctxt with + allowed_endorsements = + Signature.Public_key_hash.Map.add k (d,s,true) ctxt.allowed_endorsements } + let init_endorsements ctxt allowed_endorsements = - { ctxt with allowed_endorsements } + if Signature.Public_key_hash.Map.is_empty allowed_endorsements + then assert false (* can't initialize to empty *) + else begin + if Signature.Public_key_hash.Map.is_empty ctxt.allowed_endorsements + then { ctxt with allowed_endorsements } + else assert false (* can't initialize twice *) + end + let allowed_endorsements ctxt = ctxt.allowed_endorsements diff --git a/src/proto_alpha/lib_protocol/src/raw_context.mli b/src/proto_alpha/lib_protocol/src/raw_context.mli index 94b7b5771..7d3b8455a 100644 --- a/src/proto_alpha/lib_protocol/src/raw_context.mli +++ b/src/proto_alpha/lib_protocol/src/raw_context.mli @@ -205,12 +205,19 @@ val record_internal_nonce: context -> int -> context (** Check is the internal operation nonce has been taken. *) val internal_nonce_already_recorded: context -> int -> bool -val record_endorsement: - context -> Signature.Public_key_hash.t -> context +(** Returns a map where to each endorser's pkh is associated the list of its + endorsing slots (in decreasing order) for a given level. *) val allowed_endorsements: context -> - (Signature.Public_key.t * int list) Signature.Public_key_hash.Map.t + (Signature.Public_key.t * int list * bool) Signature.Public_key_hash.Map.t + +(** Initializes the map of allowed endorsements, this function must only be + called once. *) val init_endorsements: context -> - (Signature.Public_key.t * int list) Signature.Public_key_hash.Map.t -> + (Signature.Public_key.t * int list * bool) Signature.Public_key_hash.Map.t -> context + +(** Marks an endorsment in the map as used. *) +val record_endorsement: + context -> Signature.Public_key_hash.t -> context