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
|
|
|
|
2018-06-18 18:50:23 +04:00
|
|
|
module State = Daemon_state.Make(struct let name = "endorsement" end)
|
2016-09-08 21:13:10 +04:00
|
|
|
|
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]
|
2018-06-13 07:35:26 +04:00
|
|
|
(chain, block) >>=? function
|
2018-06-18 20:56:52 +04:00
|
|
|
| [{ slots }] -> return (Some slots)
|
|
|
|
| _ -> return None
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2018-04-16 02:44:24 +04:00
|
|
|
let inject_endorsement
|
|
|
|
(cctxt : #Proto_alpha.full)
|
2018-06-14 11:31:10 +04:00
|
|
|
?(chain = `Main) block hash level ?async
|
2018-05-06 04:08:39 +04:00
|
|
|
src_sk pkh =
|
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
|
2018-02-21 22:52:21 +04:00
|
|
|
~level:level
|
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-06-18 14:34:03 +04:00
|
|
|
State.record cctxt pkh level >>=? fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
return oph
|
|
|
|
|
2018-05-28 17:36:18 +04:00
|
|
|
|
2018-05-22 17:13:03 +04:00
|
|
|
let check_endorsement cctxt level pkh =
|
2018-06-18 14:34:03 +04:00
|
|
|
State.get 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 =
|
2018-06-18 14:34:03 +04:00
|
|
|
State.get cctxt pkh >>=? function
|
2018-05-22 17:22:38 +04:00
|
|
|
| None -> return false
|
|
|
|
| Some last_lvl ->
|
2018-06-13 07:35:26 +04:00
|
|
|
return (Raw_level.(last_lvl >= new_lvl))
|
2018-05-22 17:22:38 +04:00
|
|
|
|
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 () ->
|
2018-05-23 18:29:19 +04:00
|
|
|
previously_endorsed_level cctxt src_pkh level >>=? function
|
|
|
|
| true ->
|
|
|
|
cctxt#error "Level %a : previously endorsed."
|
|
|
|
Raw_level.pp level
|
|
|
|
| false ->
|
|
|
|
begin
|
|
|
|
match slots with
|
|
|
|
| Some slots -> return slots
|
|
|
|
| None ->
|
|
|
|
get_signing_slots
|
|
|
|
cctxt ~chain block src_pkh level >>=? function
|
2018-06-18 20:56:52 +04:00
|
|
|
| None -> cctxt#error "No slot found at level %a" Raw_level.pp level
|
|
|
|
| Some slots -> return slots
|
2018-05-23 18:29:19 +04:00
|
|
|
end >>=? fun slots ->
|
2018-06-14 11:31:10 +04:00
|
|
|
Shell_services.Blocks.hash cctxt ~chain ~block () >>=? fun hash ->
|
2018-05-06 04:08:39 +04:00
|
|
|
inject_endorsement cctxt ~chain ?async block hash level src_sk src_pkh >>=? fun oph ->
|
2018-05-23 18:29:19 +04:00
|
|
|
Client_keys.get_key cctxt src_pkh >>=? fun (name, _pk, _sk) ->
|
|
|
|
cctxt#message
|
2018-05-06 04:08:39 +04:00
|
|
|
"Injected endorsement level %a, contract %s '%a', slots @[<h>%a@]"
|
2018-05-23 18:29:19 +04:00
|
|
|
Raw_level.pp level
|
|
|
|
name
|
2018-05-06 04:08:39 +04:00
|
|
|
Operation_hash.pp_short oph
|
|
|
|
(Format.pp_print_list Format.pp_print_int) slots
|
|
|
|
>>=
|
2018-05-23 18:29:19 +04:00
|
|
|
fun () -> return oph
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
(** Worker *)
|
|
|
|
|
|
|
|
type state = {
|
|
|
|
delegates: public_key_hash list ;
|
2018-06-06 17:33:28 +04:00
|
|
|
delay: int64 ;
|
2018-06-13 12:03:29 +04:00
|
|
|
(* invariant: only one slot per delegate *)
|
|
|
|
mutable to_endorse : endorsement list ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
and endorsement = {
|
|
|
|
time: Time.t ;
|
2018-06-13 12:03:29 +04:00
|
|
|
timeout: unit Lwt.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
delegate: public_key_hash ;
|
2017-11-01 15:07:33 +04:00
|
|
|
block: Client_baking_blocks.block_info ;
|
2018-06-13 12:03:29 +04:00
|
|
|
slots: int list ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
|
2018-05-23 18:29:19 +04:00
|
|
|
let create_state delegates delay =
|
2018-06-13 12:03:29 +04:00
|
|
|
{ delegates ; delay ; to_endorse=[] }
|
2016-09-08 21:13:10 +04:00
|
|
|
|
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 ->
|
2018-05-28 17:36:18 +04:00
|
|
|
return (List.map (fun (_, pkh, _, _) -> pkh) keys)
|
2017-04-05 03:02:10 +04:00
|
|
|
| _ :: _ as delegates ->
|
|
|
|
return delegates
|
2017-02-28 11:18:06 +04:00
|
|
|
|
2018-06-13 12:03:29 +04:00
|
|
|
let endorse_for_delegate cctxt { delegate ; block ; slots ; } =
|
|
|
|
let hash = block.hash in
|
|
|
|
let b = `Hash (hash, 0) in
|
2018-06-12 20:46:12 +04:00
|
|
|
let level = block.level in
|
2018-06-13 12:03:29 +04:00
|
|
|
Client_keys.get_key cctxt delegate >>=? fun (name, _pk, sk) ->
|
|
|
|
lwt_debug "Endorsing %a for %s (level %a using %d slots)!"
|
|
|
|
Block_hash.pp_short hash name
|
|
|
|
Raw_level.pp level
|
|
|
|
(List.length slots) >>= fun () ->
|
|
|
|
inject_endorsement cctxt
|
2018-06-14 11:31:10 +04:00
|
|
|
b hash level
|
2018-05-06 04:08:39 +04:00
|
|
|
sk delegate >>=? fun oph ->
|
2018-06-13 12:03:29 +04:00
|
|
|
lwt_log_info
|
|
|
|
"Injected endorsement for block '%a' \
|
|
|
|
(level %a, contract %s) '%a'"
|
|
|
|
Block_hash.pp_short hash
|
|
|
|
Raw_level.pp level
|
|
|
|
name
|
|
|
|
Operation_hash.pp_short oph >>= fun () ->
|
2018-06-14 11:31:10 +04:00
|
|
|
lwt_log_info
|
2018-06-13 12:03:29 +04:00
|
|
|
"Injected endorsement level %a, contract %s '%a'"
|
|
|
|
Raw_level.pp level
|
|
|
|
name
|
|
|
|
Operation_hash.pp_short oph >>= fun () ->
|
|
|
|
return ()
|
|
|
|
|
2018-05-23 18:29:19 +04:00
|
|
|
let endorse_for cctxt = function
|
2018-06-13 12:03:29 +04:00
|
|
|
| [] -> return []
|
2018-06-14 11:30:05 +04:00
|
|
|
| endorsements ->
|
2018-06-13 12:03:29 +04:00
|
|
|
let done_waiting, still_waiting, errored =
|
|
|
|
List.fold_left
|
2018-06-14 11:30:05 +04:00
|
|
|
(fun (r, s, f) ({ timeout } as endorsement) -> match Lwt.state timeout with
|
|
|
|
| Lwt.Return () -> (endorsement :: r, s, f)
|
|
|
|
| Lwt.Sleep -> (r, endorsement :: s, f)
|
|
|
|
| Lwt.Fail _ -> (r, s, endorsement :: f)
|
2018-06-13 12:03:29 +04:00
|
|
|
)
|
|
|
|
([], [], [])
|
2018-06-14 11:30:05 +04:00
|
|
|
endorsements
|
2018-06-13 12:03:29 +04:00
|
|
|
in
|
|
|
|
iter_p (endorse_for_delegate cctxt) done_waiting >>=? fun () ->
|
2018-06-14 11:30:35 +04:00
|
|
|
Lwt_list.iter_p (fun {timeout} ->
|
|
|
|
match Lwt.state timeout with
|
|
|
|
| Lwt.Fail f -> lwt_log_error "Endorsement failure: %s" (Printexc.to_string f)
|
|
|
|
| _ -> Lwt.return_unit) errored >>= fun () ->
|
2018-06-13 12:03:29 +04:00
|
|
|
return still_waiting
|
2017-02-15 23:39:38 +04:00
|
|
|
|
2018-06-14 08:47:42 +04:00
|
|
|
|
2018-06-13 07:35:26 +04:00
|
|
|
let allowed_to_endorse cctxt state (block: Client_baking_blocks.block_info) delegate time =
|
|
|
|
Client_keys.Public_key_hash.name cctxt delegate >>=? fun name ->
|
|
|
|
lwt_log_info "Checking if allowed to endorse block %a for %s"
|
|
|
|
Block_hash.pp_short block.hash name >>= fun () ->
|
|
|
|
let b = `Hash (block.hash, 0) in
|
2018-06-12 20:46:12 +04:00
|
|
|
let level = block.level in
|
2018-06-13 07:35:26 +04:00
|
|
|
get_signing_slots cctxt b delegate level >>=? fun slots ->
|
2018-06-18 20:56:52 +04:00
|
|
|
match slots with
|
|
|
|
| None ->
|
|
|
|
lwt_debug "No slot found for %a/%s"
|
|
|
|
Block_hash.pp_short block.hash name >>= fun () ->
|
|
|
|
return ()
|
|
|
|
| Some slots ->
|
|
|
|
lwt_debug "Found slots for %a/%s (%d)"
|
|
|
|
Block_hash.pp_short block.hash name (List.length slots) >>= fun () ->
|
|
|
|
previously_endorsed_level cctxt delegate level >>=? function
|
|
|
|
| true ->
|
|
|
|
lwt_debug "Level %a (or higher) previously endorsed: do not endorse."
|
|
|
|
Raw_level.pp level >>= return
|
|
|
|
| false ->
|
|
|
|
match Client_baking_scheduling.sleep_until time with
|
2018-06-14 08:47:42 +04:00
|
|
|
| None ->
|
2018-06-18 20:56:52 +04:00
|
|
|
lwt_debug "Endorsment opportunity is passed." >>= fun () ->
|
2018-06-14 08:47:42 +04:00
|
|
|
return ()
|
2018-06-18 20:56:52 +04:00
|
|
|
| Some timeout ->
|
|
|
|
let neu = { time ; timeout ; delegate ; block; slots } in
|
|
|
|
match List.find_opt (fun { delegate = d } -> delegate = d) state.to_endorse with
|
|
|
|
| None ->
|
|
|
|
state.to_endorse <- neu :: state.to_endorse;
|
|
|
|
return ()
|
|
|
|
| Some old ->
|
|
|
|
if Fitness.compare old.block.fitness neu.block.fitness < 0 then begin
|
|
|
|
let without_old =
|
|
|
|
List.filter (fun to_end -> to_end <> old) state.to_endorse in
|
|
|
|
state.to_endorse <- neu :: without_old;
|
|
|
|
return ()
|
|
|
|
end else
|
|
|
|
lwt_debug "Block %a is not the fittest: do not endorse."
|
|
|
|
Block_hash.pp_short neu.block.hash >>= fun () ->
|
|
|
|
return ()
|
2018-06-13 07:35:26 +04:00
|
|
|
|
2018-06-13 07:32:22 +04:00
|
|
|
let prepare_endorsement (cctxt : #Proto_alpha.full) ~(max_past:int64) state bi =
|
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 ->
|
2018-06-11 17:51:59 +04:00
|
|
|
let open Client_baking_blocks in
|
|
|
|
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
|
2018-06-13 07:32:22 +04:00
|
|
|
else if Time.diff (Time.now ()) bi.timestamp > max_past then
|
2018-06-11 17:51:59 +04:00
|
|
|
lwt_log_info "Ignore block %a: forged too far the past"
|
|
|
|
Block_hash.pp_short bi.hash >>= return
|
|
|
|
else
|
|
|
|
let time = Time.(add (now ()) state.delay) in
|
2018-06-13 07:35:26 +04:00
|
|
|
allowed_to_endorse cctxt state bi delegate time
|
2018-05-23 18:29:19 +04:00
|
|
|
)
|
2017-04-05 03:02:10 +04:00
|
|
|
delegates
|
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
let compute_timeout state =
|
|
|
|
match state.to_endorse with
|
2018-06-19 00:31:32 +04:00
|
|
|
| [] -> Lwt_utils.never_ending ()
|
2018-06-13 12:03:29 +04:00
|
|
|
| to_ends ->
|
|
|
|
Lwt.choose (List.map (fun to_end -> to_end.timeout) to_ends)
|
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2018-05-23 18:29:19 +04:00
|
|
|
let check_error f =
|
|
|
|
f >>= function
|
|
|
|
| Ok () -> Lwt.return_unit
|
2018-06-13 07:35:26 +04:00
|
|
|
| Error errs -> lwt_log_error "Error while endorsing:@\n%a" pp_print_error errs
|
2018-06-06 17:33:28 +04:00
|
|
|
|
2018-06-13 10:38:16 +04:00
|
|
|
let create
|
|
|
|
(cctxt: #Proto_alpha.full)
|
|
|
|
~max_past
|
|
|
|
~delay
|
|
|
|
contracts
|
|
|
|
block_stream
|
|
|
|
bi =
|
2018-06-13 07:35:26 +04:00
|
|
|
lwt_log_info "Preparing endorsement daemon" >>= fun () ->
|
2018-06-13 10:38:16 +04:00
|
|
|
(* statefulness setup *)
|
|
|
|
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
|
|
|
|
let state = create_state contracts (Int64.of_int delay) in
|
|
|
|
|
|
|
|
(* main loop *)
|
|
|
|
let rec worker_loop () =
|
|
|
|
begin
|
|
|
|
let timeout = compute_timeout state in
|
|
|
|
Lwt.choose [ (timeout >|= fun () -> `Timeout) ;
|
|
|
|
(get_block () >|= fun b -> `Hash b) ] >>= function
|
2018-06-19 00:42:40 +04:00
|
|
|
| `Hash None ->
|
|
|
|
last_get_block := None;
|
|
|
|
lwt_log_error "Connection to node lost, exiting." >>= fun () ->
|
|
|
|
exit 1
|
|
|
|
| `Hash (Some (Error _)) ->
|
2018-06-13 10:38:16 +04:00
|
|
|
last_get_block := None;
|
|
|
|
Lwt.return_unit
|
|
|
|
| `Hash (Some (Ok bi)) ->
|
|
|
|
last_get_block := None;
|
2018-06-14 05:36:48 +04:00
|
|
|
check_error @@ prepare_endorsement cctxt ~max_past state bi
|
2018-06-13 10:38:16 +04:00
|
|
|
| `Timeout ->
|
2018-06-13 12:03:29 +04:00
|
|
|
begin
|
|
|
|
endorse_for cctxt state.to_endorse >>= function
|
|
|
|
| Ok still_waiting ->
|
|
|
|
state.to_endorse <- still_waiting ;
|
|
|
|
Lwt.return_unit
|
|
|
|
| Error errs ->
|
|
|
|
lwt_log_error "Error while endorsing:@\n%a" pp_print_error errs
|
|
|
|
end
|
2018-06-13 10:38:16 +04:00
|
|
|
end >>= fun () ->
|
|
|
|
worker_loop () in
|
|
|
|
|
|
|
|
(* ignition *)
|
|
|
|
check_error (prepare_endorsement cctxt ~max_past state bi) >>= fun () ->
|
|
|
|
lwt_log_info "Starting endorsement daemon" >>= fun () ->
|
|
|
|
worker_loop ()
|
|
|
|
|
|
|
|
(* A wrapper around the main create function (above) to wait for the initial
|
|
|
|
block. *)
|
|
|
|
let create
|
|
|
|
(cctxt: #Proto_alpha.full)
|
|
|
|
?(max_past=110L)
|
|
|
|
~delay
|
|
|
|
contracts
|
|
|
|
(block_stream: Client_baking_blocks.block_info tzresult Lwt_stream.t) =
|
2018-06-14 10:24:33 +04:00
|
|
|
Client_baking_scheduling.wait_for_first_block
|
2018-06-19 00:42:40 +04:00
|
|
|
~info:lwt_log_info
|
2018-06-14 10:24:33 +04:00
|
|
|
block_stream
|
|
|
|
(create cctxt ~max_past ~delay contracts block_stream)
|