Alpha: reintroduce duplicate endorsement error

This commit is contained in:
Marco Stronati 2018-06-16 18:19:53 +02:00 committed by Grégoire Henry
parent 1ab9a7a72c
commit b5c275c5fa
10 changed files with 94 additions and 60 deletions

View File

@ -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

View File

@ -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,14 +609,20 @@ 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) ->
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
@ -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

View File

@ -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

View File

@ -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 =

View File

@ -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

View File

@ -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)

View File

@ -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) =

View File

@ -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

View File

@ -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 =
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.remove k ctxt.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

View File

@ -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