ligo/src/client/embedded/alpha/baker/client_mining_endorsement.ml

380 lines
13 KiB
OCaml
Raw Normal View History

2016-09-08 21:13:10 +04:00
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2016. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
open Logging.Client.Endorsement
open Cli_entries
module Ed25519 = Environment.Ed25519
2016-09-08 21:13:10 +04:00
module State : sig
val get_endorsement:
Client_commands.context ->
2016-09-08 21:13:10 +04:00
Raw_level.t ->
int ->
(Block_hash.t * Operation_hash.t) option tzresult Lwt.t
val record_endorsement:
Client_commands.context ->
2016-09-08 21:13:10 +04:00
Raw_level.t ->
Block_hash.t ->
int -> Operation_hash.t -> unit tzresult Lwt.t
end = struct
module LevelMap = Map.Make(Raw_level)
type t = (int * Block_hash.t * Operation_hash.t) list LevelMap.t
let encoding : t Data_encoding.t =
let open Data_encoding in
conv
(fun x -> LevelMap.bindings x)
2017-02-15 20:20:10 +04:00
(fun l ->
List.fold_left
(fun x (y, z) -> LevelMap.add y z x)
LevelMap.empty l)
2016-09-08 21:13:10 +04:00
(list (obj2
(req "level" Raw_level.encoding)
(req "endorsement"
(list (obj3
(req "slot" int31)
(req "block" Block_hash.encoding)
(req "operation" Operation_hash.encoding))))))
let filename cctxt =
Client_commands.(Filename.concat cctxt.config.base_dir "endorsements")
2016-09-08 21:13:10 +04:00
let load cctxt =
let filename = filename cctxt in
2016-09-08 21:13:10 +04:00
if not (Sys.file_exists filename) then return LevelMap.empty else
Data_encoding_ezjsonm.read_file filename >>= function
| Error _ ->
2017-02-15 20:20:10 +04:00
cctxt.Client_commands.error
"couldn't to read the endorsement file"
| Ok json ->
2016-09-08 21:13:10 +04:00
match Data_encoding.Json.destruct encoding json with
| exception _ -> (* TODO print_error *)
2017-02-15 20:20:10 +04:00
cctxt.Client_commands.error
"didn't understand the endorsement file"
2016-09-08 21:13:10 +04:00
| map ->
return map
let save cctxt map =
2016-09-08 21:13:10 +04:00
Lwt.catch
(fun () ->
let dirname = Client_commands.(cctxt.config.base_dir) in
(if not (Sys.file_exists dirname) then Lwt_utils.create_dir dirname
2016-09-08 21:13:10 +04:00
else Lwt.return ()) >>= fun () ->
let filename = filename cctxt in
2016-09-08 21:13:10 +04:00
let json = Data_encoding.Json.construct encoding map in
Data_encoding_ezjsonm.write_file filename json >>= function
| Error _ -> failwith "Json.write_file"
| Ok () -> return ())
2016-09-08 21:13:10 +04:00
(fun exn ->
2017-02-15 20:20:10 +04:00
cctxt.Client_commands.error
"could not write the endorsement file: %s."
2016-09-08 21:13:10 +04:00
(Printexc.to_string exn))
let lock = Lwt_mutex.create ()
let get_endorsement cctxt level slot =
2016-09-08 21:13:10 +04:00
Lwt_mutex.with_lock lock
(fun () ->
load cctxt >>=? fun map ->
2016-09-08 21:13:10 +04:00
try
let _, block, op =
LevelMap.find level map
|> List.find (fun (slot',_,_) -> slot = slot') in
return (Some (block, op))
with Not_found -> return None)
let record_endorsement cctxt level hash slot oph =
2016-09-08 21:13:10 +04:00
Lwt_mutex.with_lock lock
(fun () ->
load cctxt >>=? fun map ->
2016-09-08 21:13:10 +04:00
let previous =
try LevelMap.find level map
with Not_found -> [] in
save cctxt
2016-09-08 21:13:10 +04:00
(LevelMap.add level ((slot, hash, oph) :: previous) map))
end
let get_signing_slots cctxt ?max_priority block delegate level =
Client_proto_rpcs.Helpers.Rights.endorsement_rights_for_delegate cctxt
2016-09-08 21:13:10 +04:00
?max_priority ~first_level:level ~last_level:level
block delegate () >>=? fun possibilities ->
let slots =
List.map (fun (_,slot) -> slot)
@@ List.filter (fun (l, _) -> l = level) possibilities in
2016-09-08 21:13:10 +04:00
return slots
let inject_endorsement cctxt
2016-09-08 21:13:10 +04:00
block level ?wait ?force
src_sk source slot =
2017-02-25 16:24:53 +04:00
Client_blocks.get_block_hash cctxt block >>= fun block_hash ->
Client_node_rpcs.Blocks.net cctxt block >>= fun net ->
Client_proto_rpcs.Helpers.Forge.Delegate.endorsement cctxt
2016-09-08 21:13:10 +04:00
block
~net
~source
~block:block_hash
~slot:slot
() >>=? fun bytes ->
let signed_bytes = Ed25519.Signature.append src_sk bytes in
2017-02-15 20:20:10 +04:00
Client_node_rpcs.inject_operation
cctxt ?force ?wait signed_bytes >>=? fun oph ->
State.record_endorsement cctxt level block_hash slot oph >>=? fun () ->
2016-09-08 21:13:10 +04:00
return oph
let previously_endorsed_slot cctxt level slot =
State.get_endorsement cctxt level slot >>=? function
2016-09-08 21:13:10 +04:00
| None -> return false
| Some _ -> return true
let check_endorsement cctxt level slot =
State.get_endorsement cctxt level slot >>=? function
2016-09-08 21:13:10 +04:00
| None -> return ()
| Some (block, _) ->
Error_monad.failwith
2016-09-08 21:13:10 +04:00
"Already signed block %a at level %a, slot %d"
Block_hash.pp_short block Raw_level.pp level slot
let forge_endorsement cctxt
2016-09-08 21:13:10 +04:00
block ?(force = false)
~src_sk ?slot ?max_priority src_pk =
2017-03-14 20:17:58 +04:00
let block =
match block with
| `Prevalidation -> `Head 0
| `Test_prevalidation -> `Test_head 0
| _ -> block in
let src_pkh = Ed25519.Public_key.hash src_pk in
2017-03-14 20:17:58 +04:00
Client_proto_rpcs.Context.level cctxt block >>=? fun level ->
let level = Raw_level.succ level.level in
2016-09-08 21:13:10 +04:00
begin
match slot with
| Some slot -> return slot
| None ->
2017-02-15 20:20:10 +04:00
get_signing_slots
cctxt ?max_priority block src_pkh level >>=? function
2016-09-08 21:13:10 +04:00
| slot::_ -> return slot
| [] -> cctxt.error "No slot found at level %a" Raw_level.pp level
2016-09-08 21:13:10 +04:00
end >>=? fun slot ->
2017-02-15 20:20:10 +04:00
begin
if force then return ()
else check_endorsement cctxt level slot
end >>=? fun () ->
inject_endorsement cctxt
2016-09-08 21:13:10 +04:00
block level ~wait:true ~force
src_sk src_pk slot
(** Worker *)
type state = {
delegates: public_key_hash list ;
mutable best: Client_mining_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 ;
block: Client_mining_blocks.block_info ;
slot: int;
}
let create_state delegates best delay =
2016-09-08 21:13:10 +04:00
{ delegates ;
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
let get_delegates cctxt state =
match state.delegates with
| [] -> Client_keys.get_keys cctxt >|= List.map (fun (_,pkh,_,_) -> pkh)
| _ :: _ as delegates -> Lwt.return delegates
let drop_old_endorsement ~before state =
state.to_endorse <-
List.filter
(fun { block } -> Fitness.compare before block.fitness <= 0)
state.to_endorse
let schedule_endorsements cctxt state bis =
2016-09-08 21:13:10 +04:00
let may_endorse (block: Client_mining_blocks.block_info) delegate time =
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 () ->
let b = `Hash block.hash in
let level = Raw_level.succ block.level.level in
get_signing_slots cctxt b delegate level >>=? fun slots ->
2016-09-08 21:13:10 +04:00
lwt_debug "Found slots for %a/%s (%d)"
Block_hash.pp_short block.hash name (List.length slots) >>= fun () ->
iter_p
(fun slot ->
if Fitness.compare state.best.fitness block.fitness < 0 then begin
state.best <- block ;
drop_old_endorsement ~before:block.fitness state ;
end ;
previously_endorsed_slot cctxt level slot >>=? function
2016-09-08 21:13:10 +04:00
| true ->
lwt_debug "slot %d: previously endorsed." slot >>= fun () ->
return ()
| false ->
try
let same_slot e =
e.block.level = block.level && e.slot = slot in
let old = List.find same_slot state.to_endorse in
2017-02-15 20:20:10 +04:00
if Fitness.compare old.block.fitness block.fitness < 0
then begin
2016-09-08 21:13:10 +04:00
lwt_log_info
"Schedule endorsement for block %a \
\ (level %a, slot %d, time %a) (replace block %a)"
Block_hash.pp_short block.hash
Raw_level.pp level
slot
Time.pp_hum time
Block_hash.pp_short old.block.hash
>>= fun () ->
state.to_endorse <-
insert
{ time ; delegate ; block ; slot }
2017-02-15 20:20:10 +04:00
(List.filter
(fun e -> not (same_slot e))
state.to_endorse) ;
2016-09-08 21:13:10 +04:00
return ()
end else begin
2017-02-15 20:20:10 +04:00
lwt_debug
"slot %d: better pending endorsement"
slot >>= fun () ->
2016-09-08 21:13:10 +04:00
return ()
end
with Not_found ->
lwt_log_info
"Schedule endorsement for block %a \
\ (level %a, slot %d, time %a)"
Block_hash.pp_short block.hash
Raw_level.pp level
slot
Time.pp_hum time >>= fun () ->
state.to_endorse <-
insert { time ; delegate ; block ; slot } state.to_endorse ;
return ())
slots in
let time = Time.(add (now ()) state.delay) in
get_delegates cctxt state >>= fun delegates ->
2016-09-08 21:13:10 +04:00
iter_p
(fun delegate ->
iter_p
(fun bi -> may_endorse bi delegate time)
bis)
delegates >>= 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
let endorse cctxt state =
2016-09-08 21:13:10 +04:00
let to_endorse = pop_endorsements state in
iter_p
(fun { delegate ; block ; slot } ->
2016-09-08 21:13:10 +04:00
let hash = block.hash in
let b = `Hash hash in
let level = Raw_level.succ block.level.level in
previously_endorsed_slot cctxt level slot >>=? function
2016-09-08 21:13:10 +04:00
| true -> return ()
| false ->
Client_keys.get_key cctxt delegate >>=? fun (name, pk, sk) ->
2016-09-08 21:13:10 +04:00
lwt_debug "Endorsing %a for %s (slot %d)!"
Block_hash.pp_short hash name slot >>= fun () ->
inject_endorsement cctxt
2016-09-08 21:13:10 +04:00
b level ~wait:false ~force:true
sk pk slot >>=? fun oph ->
cctxt.message
2016-09-08 21:13:10 +04:00
"Injected endorsement for block '%a' \
\ (level %a, slot %d, contract %s) '%a'"
Block_hash.pp_short hash
Raw_level.pp level
slot name
Operation_hash.pp_short oph >>= fun () ->
2016-09-08 21:13:10 +04:00
return ())
to_endorse
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)
let create cctxt ~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
| None | Some [] ->
cctxt.Client_commands.error "Can't fetch the current block head."
| Some (bi :: _ as initial_heads) ->
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
let state = create_state contracts bi (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
| `Hash None ->
Lwt.return_unit
| `Hash (Some bis) ->
Lwt.cancel timeout ;
last_get_block := None ;
schedule_endorsements cctxt state bis >>= fun () ->
2016-09-08 21:13:10 +04:00
worker_loop ()
| `Timeout ->
begin
endorse cctxt state >>= function
2016-09-08 21:13:10 +04:00
| Ok () -> Lwt.return_unit
| Error errs ->
lwt_log_error "Error while endorsing:\n%a"
pp_print_error
errs >>= fun () ->
Lwt.return_unit
end >>= fun () ->
worker_loop () in
schedule_endorsements cctxt state initial_heads >>= fun () ->
2016-09-08 21:13:10 +04:00
worker_loop ()