2016-09-08 21:13:10 +04:00
|
|
|
(**************************************************************************)
|
|
|
|
(* *)
|
2018-02-06 00:17:03 +04:00
|
|
|
(* Copyright (c) 2014 - 2018. *)
|
2016-09-08 21:13:10 +04:00
|
|
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
|
|
|
(* *)
|
|
|
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
|
|
|
(* *)
|
|
|
|
(**************************************************************************)
|
|
|
|
|
2018-01-29 04:06:47 +04:00
|
|
|
open Proto_alpha
|
2018-02-11 22:17:39 +04:00
|
|
|
open Alpha_context
|
2018-01-29 04:06:47 +04:00
|
|
|
|
2018-06-01 01:05:00 +04:00
|
|
|
include Logging.Make(struct let name = "client.endorsement" end)
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
module State : sig
|
|
|
|
|
|
|
|
val get_endorsement:
|
2018-02-14 18:20:03 +04:00
|
|
|
#Client_context.wallet ->
|
2018-05-22 17:13:03 +04:00
|
|
|
Signature.Public_key_hash.t ->
|
|
|
|
Raw_level.t option tzresult Lwt.t
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
val record_endorsement:
|
2018-02-14 18:20:03 +04:00
|
|
|
#Client_context.wallet ->
|
2018-05-22 17:13:03 +04:00
|
|
|
Signature.Public_key_hash.t ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Raw_level.t ->
|
2018-05-22 17:13:03 +04:00
|
|
|
unit tzresult Lwt.t
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
end = struct
|
|
|
|
|
2018-05-22 17:13:03 +04:00
|
|
|
type t = (string * Raw_level.t) list
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let encoding : t Data_encoding.t =
|
2018-05-22 17:13:03 +04:00
|
|
|
Data_encoding.(
|
|
|
|
list (obj2
|
|
|
|
(req "delegate" string)
|
|
|
|
(req "last_level" Raw_level.encoding)
|
|
|
|
))
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-11-07 20:38:11 +04:00
|
|
|
let name =
|
|
|
|
"endorsements"
|
|
|
|
|
2018-02-14 18:20:03 +04:00
|
|
|
let load (wallet : #Client_context.wallet) =
|
2018-05-22 17:13:03 +04:00
|
|
|
wallet#load name encoding ~default:[]
|
2017-11-07 20:38:11 +04:00
|
|
|
|
2018-05-22 17:13:03 +04:00
|
|
|
let save (wallet : #Client_context.wallet) list =
|
|
|
|
wallet#write name list encoding
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
|
2018-05-22 17:13:03 +04:00
|
|
|
let get_endorsement (wallet : #Client_context.wallet) (delegate_key:Signature.public_key_hash) =
|
|
|
|
wallet#with_lock
|
2016-09-08 21:13:10 +04:00
|
|
|
(fun () ->
|
2018-05-22 17:13:03 +04:00
|
|
|
load wallet >>=? fun l ->
|
|
|
|
return (List.assoc_opt (Signature.Public_key_hash.to_short_b58check delegate_key) l)
|
|
|
|
)
|
|
|
|
|
|
|
|
let record_endorsement (wallet : #Client_context.wallet) (delegate:Signature.public_key_hash) (new_lvl:Raw_level.t) =
|
|
|
|
begin
|
|
|
|
wallet#with_lock (fun () ->
|
|
|
|
begin
|
|
|
|
load wallet >>=? fun l ->
|
|
|
|
let delegate_key = Signature.Public_key_hash.to_short_b58check delegate
|
|
|
|
in
|
2018-05-22 21:40:47 +04:00
|
|
|
let rec remove_old acc = function
|
|
|
|
| [] -> List.rev acc
|
|
|
|
| ((_,lvl) as hd)::tl ->
|
|
|
|
if Raw_level.diff new_lvl lvl > 50l (*?*) then
|
|
|
|
remove_old (hd::acc) tl
|
|
|
|
else
|
|
|
|
List.rev acc
|
|
|
|
in
|
|
|
|
save wallet ((delegate_key, new_lvl)::
|
|
|
|
List.remove_assoc delegate_key (remove_old [] l))
|
2018-05-22 17:13:03 +04:00
|
|
|
end)
|
|
|
|
end
|
2016-09-08 21:13:10 +04:00
|
|
|
end
|
|
|
|
|
2018-04-20 16:55:07 +04:00
|
|
|
let get_signing_slots cctxt ?(chain = `Main) block delegate level =
|
|
|
|
Alpha_services.Delegate.Endorsing_rights.get cctxt
|
|
|
|
~levels:[level]
|
|
|
|
~delegates:[delegate]
|
|
|
|
(chain, block) >>=? fun possibilities ->
|
|
|
|
match possibilities with
|
|
|
|
| [{ slots }] -> return slots
|
|
|
|
| _ -> return []
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2018-04-16 02:44:24 +04:00
|
|
|
let inject_endorsement
|
|
|
|
(cctxt : #Proto_alpha.full)
|
|
|
|
?(chain = `Main) block level ?async
|
2018-05-22 17:13:03 +04:00
|
|
|
src_sk slots pkh =
|
2018-04-22 16:40:44 +04:00
|
|
|
Shell_services.Blocks.hash cctxt ~chain ~block () >>=? fun hash ->
|
2018-04-30 21:06:06 +04:00
|
|
|
Alpha_services.Forge.endorsement cctxt
|
2018-04-16 02:44:24 +04:00
|
|
|
(chain, block)
|
|
|
|
~branch:hash
|
|
|
|
~block:hash
|
2018-02-21 22:52:21 +04:00
|
|
|
~level:level
|
|
|
|
~slots
|
2016-09-08 21:13:10 +04:00
|
|
|
() >>=? fun bytes ->
|
2018-06-17 02:07:58 +04:00
|
|
|
Client_keys.append cctxt
|
2018-05-26 15:22:47 +04:00
|
|
|
src_sk ~watermark:Endorsement bytes >>=? fun signed_bytes ->
|
2018-04-22 16:40:44 +04:00
|
|
|
Shell_services.Injection.operation cctxt ?async ~chain signed_bytes >>=? fun oph ->
|
2018-05-22 17:13:03 +04:00
|
|
|
State.record_endorsement cctxt pkh level >>=? fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
return oph
|
|
|
|
|
2018-05-22 17:13:03 +04:00
|
|
|
let check_endorsement cctxt level pkh =
|
|
|
|
State.get_endorsement cctxt pkh >>=? function
|
2016-09-08 21:13:10 +04:00
|
|
|
| None -> return ()
|
2018-05-22 17:13:03 +04:00
|
|
|
| Some recorded_level ->
|
|
|
|
if Raw_level.(level = recorded_level) then
|
|
|
|
Error_monad.failwith "Level %a already endorsed" Raw_level.pp recorded_level
|
|
|
|
else
|
|
|
|
return ()
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2018-05-22 17:22:38 +04:00
|
|
|
let previously_endorsed_level cctxt pkh new_lvl =
|
|
|
|
State.get_endorsement cctxt pkh >>=? function
|
|
|
|
| None -> return false
|
|
|
|
| Some last_lvl ->
|
|
|
|
return (not Raw_level.(last_lvl < new_lvl))
|
|
|
|
|
2018-02-16 21:10:18 +04:00
|
|
|
let forge_endorsement (cctxt : #Proto_alpha.full)
|
2018-05-22 17:22:38 +04:00
|
|
|
?(chain = `Main) block ?async
|
|
|
|
~src_sk ?slots src_pk =
|
2018-04-05 19:35:35 +04:00
|
|
|
let src_pkh = Signature.Public_key.hash src_pk in
|
2018-05-29 15:14:04 +04:00
|
|
|
Alpha_block_services.metadata cctxt
|
|
|
|
~chain ~block () >>=? fun { protocol_data = { level = { level } } } ->
|
2018-05-22 17:22:38 +04:00
|
|
|
check_endorsement cctxt level src_pkh >>=? fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
begin
|
2018-02-21 22:52:21 +04:00
|
|
|
match slots with
|
|
|
|
| Some slots -> return slots
|
2016-09-08 21:13:10 +04:00
|
|
|
| None ->
|
2017-02-15 20:20:10 +04:00
|
|
|
get_signing_slots
|
2018-04-20 16:55:07 +04:00
|
|
|
cctxt ~chain block src_pkh level >>=? function
|
2017-11-07 20:38:11 +04:00
|
|
|
| [] -> cctxt#error "No slot found at level %a" Raw_level.pp level
|
2018-02-21 22:52:21 +04:00
|
|
|
| slots -> return slots
|
|
|
|
end >>=? fun slots ->
|
2018-05-22 17:13:03 +04:00
|
|
|
check_endorsement cctxt level src_pkh >>=? fun () ->
|
2018-05-22 17:22:38 +04:00
|
|
|
inject_endorsement cctxt ~chain ?async block level src_sk slots src_pkh
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
(** Worker *)
|
|
|
|
|
|
|
|
type state = {
|
|
|
|
delegates: public_key_hash list ;
|
2017-11-01 15:07:33 +04:00
|
|
|
mutable best: Client_baking_blocks.block_info ;
|
2016-09-08 21:13:10 +04:00
|
|
|
mutable to_endorse: endorsement list ;
|
|
|
|
delay: int64;
|
|
|
|
}
|
|
|
|
and endorsement = {
|
|
|
|
time: Time.t ;
|
|
|
|
delegate: public_key_hash ;
|
2017-11-01 15:07:33 +04:00
|
|
|
block: Client_baking_blocks.block_info ;
|
2018-05-22 17:13:03 +04:00
|
|
|
slots: int list;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
|
2017-02-15 23:39:38 +04:00
|
|
|
let create_state delegates best delay =
|
2016-09-08 21:13:10 +04:00
|
|
|
{ delegates ;
|
2017-02-15 23:39:38 +04:00
|
|
|
best ;
|
2016-09-08 21:13:10 +04:00
|
|
|
to_endorse = [] ;
|
|
|
|
delay ;
|
|
|
|
}
|
|
|
|
|
|
|
|
let rec insert ({time} as e) = function
|
|
|
|
| [] -> [e]
|
|
|
|
| ({time = time'} :: _) as l when Time.compare time time' < 0 ->
|
|
|
|
e :: l
|
|
|
|
| e' :: l -> e' :: insert e l
|
|
|
|
|
2017-02-28 11:18:06 +04:00
|
|
|
let get_delegates cctxt state =
|
|
|
|
match state.delegates with
|
2017-04-05 03:02:10 +04:00
|
|
|
| [] ->
|
|
|
|
Client_keys.get_keys cctxt >>=? fun keys ->
|
|
|
|
return (List.map (fun (_,pkh,_,_) -> pkh) keys)
|
|
|
|
| _ :: _ as delegates ->
|
|
|
|
return delegates
|
2017-02-28 11:18:06 +04:00
|
|
|
|
2017-02-15 23:39:38 +04:00
|
|
|
let drop_old_endorsement ~before state =
|
|
|
|
state.to_endorse <-
|
|
|
|
List.filter
|
|
|
|
(fun { block } -> Fitness.compare before block.fitness <= 0)
|
|
|
|
state.to_endorse
|
|
|
|
|
2018-05-22 17:22:38 +04:00
|
|
|
let schedule_endorsements (cctxt : #Proto_alpha.full) ~(max_past:Time.t) state bis =
|
2017-11-01 15:07:33 +04:00
|
|
|
let may_endorse (block: Client_baking_blocks.block_info) delegate time =
|
2017-04-05 03:02:10 +04:00
|
|
|
Client_keys.Public_key_hash.name cctxt delegate >>=? fun name ->
|
2016-09-08 21:13:10 +04:00
|
|
|
lwt_log_info "May endorse block %a for %s"
|
|
|
|
Block_hash.pp_short block.hash name >>= fun () ->
|
2018-03-29 17:23:31 +04:00
|
|
|
let b = `Hash (block.hash, 0) in
|
2018-02-28 21:26:06 +04:00
|
|
|
let level = block.level.level in
|
2017-11-07 20:38:11 +04:00
|
|
|
get_signing_slots cctxt b delegate level >>=? fun slots ->
|
2018-05-22 17:13:03 +04:00
|
|
|
lwt_debug "Found %d slots for %a/%s"
|
|
|
|
(List.length slots) Block_hash.pp_short block.hash name >>= fun () ->
|
2018-05-22 17:22:38 +04:00
|
|
|
previously_endorsed_level cctxt delegate level >>=? function
|
|
|
|
| true ->
|
|
|
|
lwt_debug "Level %a : previously endorsed."
|
|
|
|
Raw_level.pp level >>= return
|
|
|
|
| false ->
|
|
|
|
if Fitness.compare state.best.fitness block.fitness < 0 then begin
|
|
|
|
state.best <- block ;
|
|
|
|
drop_old_endorsement ~before:block.fitness state ;
|
|
|
|
end ;
|
|
|
|
begin try
|
|
|
|
let same_slot endorsement =
|
|
|
|
endorsement.block.level = block.level && endorsement.slots = slots in
|
|
|
|
let old = List.find same_slot state.to_endorse in
|
|
|
|
if Fitness.compare old.block.fitness block.fitness < 0
|
|
|
|
then begin
|
|
|
|
lwt_log_info
|
|
|
|
"Schedule endorsement for block %a \
|
|
|
|
(level %a, slots { %a }, time %a) (replace block %a)"
|
|
|
|
Block_hash.pp_short block.hash
|
|
|
|
Raw_level.pp level
|
|
|
|
(Format.pp_print_list Format.pp_print_int) slots
|
|
|
|
Time.pp_hum time
|
|
|
|
Block_hash.pp_short old.block.hash
|
|
|
|
>>= fun () ->
|
|
|
|
state.to_endorse <-
|
|
|
|
insert
|
|
|
|
{ time ; delegate ; block ; slots }
|
|
|
|
(List.filter
|
|
|
|
(fun e -> not (same_slot e))
|
|
|
|
state.to_endorse) ;
|
|
|
|
return ()
|
|
|
|
end else begin
|
|
|
|
lwt_debug
|
|
|
|
"slot { %a } : better pending endorsement"
|
|
|
|
(Format.pp_print_list Format.pp_print_int) slots >>= fun () ->
|
|
|
|
return ()
|
|
|
|
end
|
|
|
|
with Not_found ->
|
|
|
|
lwt_log_info
|
|
|
|
"Schedule endorsement for block %a \
|
|
|
|
(level %a, slot { %a }, time %a)"
|
|
|
|
Block_hash.pp_short block.hash
|
|
|
|
Raw_level.pp level
|
|
|
|
(Format.pp_print_list Format.pp_print_int) slots
|
|
|
|
Time.pp_hum time >>= fun () ->
|
|
|
|
state.to_endorse <-
|
|
|
|
insert { time ; delegate ; block ; slots } state.to_endorse ;
|
|
|
|
return ()
|
2018-05-22 17:13:03 +04:00
|
|
|
end
|
|
|
|
in
|
2016-09-08 21:13:10 +04:00
|
|
|
let time = Time.(add (now ()) state.delay) in
|
2017-04-05 03:02:10 +04:00
|
|
|
get_delegates cctxt state >>=? fun delegates ->
|
2018-05-22 17:25:04 +04:00
|
|
|
iter_p
|
|
|
|
(fun delegate ->
|
|
|
|
iter_p
|
|
|
|
(fun (bi : Client_baking_blocks.block_info) ->
|
|
|
|
if Time.compare bi.timestamp (Time.now ()) > 0 then
|
|
|
|
lwt_log_info "Ignore block %a: forged in the future"
|
|
|
|
Block_hash.pp_short bi.hash >>= return
|
|
|
|
else if Time.(min (now ()) bi.timestamp > max_past) then
|
|
|
|
lwt_log_info "Ignore block %a: forged too far the past"
|
|
|
|
Block_hash.pp_short bi.hash >>= return
|
|
|
|
else
|
|
|
|
may_endorse bi delegate time)
|
|
|
|
bis)
|
2017-04-05 03:02:10 +04:00
|
|
|
delegates
|
|
|
|
|
2018-05-22 17:22:38 +04:00
|
|
|
let schedule_endorsements (cctxt : #Proto_alpha.full) ~max_past state bis =
|
|
|
|
schedule_endorsements cctxt ~max_past state bis >>= function
|
2016-09-08 21:13:10 +04:00
|
|
|
| Error exns ->
|
|
|
|
lwt_log_error
|
|
|
|
"@[<v 2>Error(s) while scheduling endorsements@,%a@]"
|
|
|
|
pp_print_error exns
|
|
|
|
| Ok () -> Lwt.return_unit
|
|
|
|
|
|
|
|
let pop_endorsements state =
|
|
|
|
let now = Time.now () in
|
|
|
|
let rec pop acc = function
|
|
|
|
| [] -> List.rev acc, []
|
|
|
|
| {time} :: _ as slots when Time.compare now time <= 0 ->
|
|
|
|
List.rev acc, slots
|
|
|
|
| slot :: slots -> pop (slot :: acc) slots in
|
|
|
|
let to_endorse, future_endorsement = pop [] state.to_endorse in
|
|
|
|
state.to_endorse <- future_endorsement ;
|
|
|
|
to_endorse
|
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let endorse cctxt state =
|
2016-09-08 21:13:10 +04:00
|
|
|
let to_endorse = pop_endorsements state in
|
2018-05-22 17:13:03 +04:00
|
|
|
iter_p (fun { time = _ ; block ; slots ; delegate } ->
|
|
|
|
let hash = block.hash in
|
|
|
|
let b = `Hash (hash, 0) in
|
|
|
|
let level = block.level.level in
|
|
|
|
Client_keys.get_key cctxt delegate >>=? fun (name, pk, sk) ->
|
|
|
|
let pkh = Signature.Public_key.hash pk in
|
|
|
|
lwt_debug "Endorsing %a for %s (slots : { %a } )!"
|
|
|
|
Block_hash.pp_short block.hash name
|
|
|
|
(Format.pp_print_list Format.pp_print_int) slots >>= fun () ->
|
|
|
|
inject_endorsement cctxt b block.level.level sk slots pkh >>=? fun oph ->
|
|
|
|
cctxt#message
|
|
|
|
"Injected endorsement for block '%a' \
|
|
|
|
(level %a, slots { %a }, contract %s) '%a'"
|
|
|
|
Block_hash.pp_short hash
|
|
|
|
Raw_level.pp level
|
|
|
|
(Format.pp_print_list Format.pp_print_int) slots name
|
|
|
|
Operation_hash.pp_short oph >>= fun () -> return ()
|
|
|
|
) to_endorse
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let compute_timeout state =
|
|
|
|
match state.to_endorse with
|
|
|
|
| [] -> Lwt_utils.never_ending
|
|
|
|
| {time} :: _ ->
|
|
|
|
let delay = (Time.diff time (Time.now ())) in
|
|
|
|
if delay <= 0L then
|
|
|
|
Lwt.return_unit
|
|
|
|
else
|
|
|
|
Lwt_unix.sleep (Int64.to_float delay)
|
|
|
|
|
2018-05-22 17:22:38 +04:00
|
|
|
|
|
|
|
let create (cctxt : #Proto_alpha.full) ?(max_past=(Time.of_seconds 110L)) ~delay contracts block_stream =
|
2016-09-08 21:13:10 +04:00
|
|
|
lwt_log_info "Starting endorsement daemon" >>= fun () ->
|
|
|
|
Lwt_stream.get block_stream >>= function
|
2018-04-16 02:44:24 +04:00
|
|
|
| None | Some (Error _) ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#error "Can't fetch the current block head."
|
2018-04-16 02:44:24 +04:00
|
|
|
| Some (Ok head) ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let last_get_block = ref None in
|
|
|
|
let get_block () =
|
|
|
|
match !last_get_block with
|
|
|
|
| None ->
|
|
|
|
let t = Lwt_stream.get block_stream in
|
|
|
|
last_get_block := Some t ;
|
|
|
|
t
|
|
|
|
| Some t -> t in
|
2018-04-16 02:44:24 +04:00
|
|
|
let state = create_state contracts head (Int64.of_int delay) in
|
2016-09-08 21:13:10 +04:00
|
|
|
let rec worker_loop () =
|
|
|
|
let timeout = compute_timeout state in
|
|
|
|
Lwt.choose [ (timeout >|= fun () -> `Timeout) ;
|
|
|
|
(get_block () >|= fun b -> `Hash b) ] >>= function
|
2017-04-05 01:35:41 +04:00
|
|
|
| `Hash (None | Some (Error _)) ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.return_unit
|
2018-04-16 02:44:24 +04:00
|
|
|
| `Hash (Some (Ok bi)) ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.cancel timeout ;
|
|
|
|
last_get_block := None ;
|
2018-05-22 17:25:04 +04:00
|
|
|
schedule_endorsements cctxt ~max_past state [ bi ] >>= fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
worker_loop ()
|
|
|
|
| `Timeout ->
|
|
|
|
begin
|
2016-12-03 16:05:02 +04:00
|
|
|
endorse cctxt state >>= function
|
2016-09-08 21:13:10 +04:00
|
|
|
| Ok () -> Lwt.return_unit
|
|
|
|
| Error errs ->
|
2018-02-28 21:26:06 +04:00
|
|
|
lwt_log_error "Error while endorsing:@\n%a"
|
2016-09-08 21:13:10 +04:00
|
|
|
pp_print_error
|
|
|
|
errs >>= fun () ->
|
|
|
|
Lwt.return_unit
|
|
|
|
end >>= fun () ->
|
2018-05-22 17:25:04 +04:00
|
|
|
worker_loop ()
|
|
|
|
in
|
|
|
|
schedule_endorsements cctxt ~max_past state [ head ] >>= fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
worker_loop ()
|