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. *)
|
|
|
|
(* *)
|
|
|
|
(**************************************************************************)
|
|
|
|
|
2017-04-05 01:35:41 +04:00
|
|
|
|
|
|
|
open Client_commands
|
2016-09-08 21:13:10 +04:00
|
|
|
open Logging.Client.Mining
|
2016-11-14 18:55:24 +04:00
|
|
|
module Ed25519 = Environment.Ed25519
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let generate_proof_of_work_nonce () =
|
|
|
|
Sodium.Random.Bigbytes.generate Constants.proof_of_work_nonce_size
|
|
|
|
|
|
|
|
let generate_seed_nonce () =
|
|
|
|
match Nonce.of_bytes @@
|
|
|
|
Sodium.Random.Bigbytes.generate Constants.nonce_length with
|
|
|
|
| Error _ -> assert false
|
|
|
|
| Ok nonce -> nonce
|
|
|
|
|
2017-02-15 20:20:10 +04:00
|
|
|
let rec compute_stamp
|
|
|
|
cctxt block delegate_sk shell mining_slot seed_nonce_hash =
|
|
|
|
Client_proto_rpcs.Constants.stamp_threshold
|
|
|
|
cctxt block >>=? fun stamp_threshold ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let rec loop () =
|
|
|
|
let proof_of_work_nonce = generate_proof_of_work_nonce () in
|
|
|
|
let unsigned_header =
|
|
|
|
Tezos_context.Block.forge_header
|
|
|
|
shell { mining_slot ; seed_nonce_hash ; proof_of_work_nonce } in
|
|
|
|
let signed_header =
|
2017-02-28 05:56:40 +04:00
|
|
|
Ed25519.Signature.append delegate_sk unsigned_header in
|
2016-09-08 21:13:10 +04:00
|
|
|
let block_hash = Block_hash.hash_bytes [signed_header] in
|
|
|
|
if Mining.check_hash block_hash stamp_threshold then
|
|
|
|
proof_of_work_nonce
|
|
|
|
else
|
|
|
|
loop () in
|
|
|
|
return (loop ())
|
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let inject_block cctxt block
|
2016-09-08 21:13:10 +04:00
|
|
|
?force
|
|
|
|
~priority ~timestamp ~fitness ~seed_nonce
|
2017-03-30 15:16:21 +04:00
|
|
|
~src_sk operation_list =
|
2016-09-08 21:13:10 +04:00
|
|
|
let block = match block with `Prevalidation -> `Head 0 | block -> block in
|
2017-04-05 01:35:41 +04:00
|
|
|
Client_node_rpcs.Blocks.info cctxt block >>=? fun bi ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let seed_nonce_hash = Nonce.hash seed_nonce in
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_proto_rpcs.Context.next_level cctxt block >>=? fun level ->
|
2017-03-30 15:16:21 +04:00
|
|
|
let operations =
|
|
|
|
Operation_list_list_hash.compute
|
|
|
|
(List.map Operation_list_hash.compute operation_list) in
|
2016-09-08 21:13:10 +04:00
|
|
|
let shell =
|
2017-02-24 20:17:53 +04:00
|
|
|
{ Store.Block_header.net_id = bi.net ; predecessor = bi.hash ;
|
2016-09-08 21:13:10 +04:00
|
|
|
timestamp ; fitness ; operations } in
|
2017-04-10 14:50:15 +04:00
|
|
|
let slot = { Block.level = level.level ; priority } in
|
2016-12-03 16:05:02 +04:00
|
|
|
compute_stamp cctxt block
|
2016-09-08 21:13:10 +04:00
|
|
|
src_sk shell slot seed_nonce_hash >>=? fun proof_of_work_nonce ->
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_proto_rpcs.Helpers.Forge.block cctxt
|
2016-09-08 21:13:10 +04:00
|
|
|
block
|
|
|
|
~net:bi.net
|
|
|
|
~predecessor:bi.hash
|
|
|
|
~timestamp
|
|
|
|
~fitness
|
|
|
|
~operations
|
|
|
|
~level:level.level
|
|
|
|
~priority:priority
|
|
|
|
~seed_nonce_hash
|
|
|
|
~proof_of_work_nonce
|
|
|
|
() >>=? fun unsigned_header ->
|
2017-02-28 05:56:40 +04:00
|
|
|
let signed_header = Ed25519.Signature.append src_sk unsigned_header in
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_node_rpcs.inject_block cctxt
|
2017-03-30 15:16:21 +04:00
|
|
|
?force signed_header operation_list >>=? fun block_hash ->
|
2016-09-08 21:13:10 +04:00
|
|
|
return block_hash
|
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let forge_block cctxt block
|
2016-09-08 21:13:10 +04:00
|
|
|
?force
|
|
|
|
?operations ?(best_effort = operations = None) ?(sort = best_effort)
|
2017-03-08 21:47:01 +04:00
|
|
|
?timestamp
|
|
|
|
~priority
|
|
|
|
~seed_nonce ~src_sk () =
|
2016-09-08 21:13:10 +04:00
|
|
|
let block =
|
|
|
|
match block with
|
|
|
|
| `Prevalidation -> `Head 0
|
|
|
|
| `Test_prevalidation -> `Test_head 0
|
|
|
|
| block -> block in
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_proto_rpcs.Context.level cctxt block >>=? fun level ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let level = Raw_level.succ level.level in
|
|
|
|
begin
|
|
|
|
match operations with
|
|
|
|
| None ->
|
2017-02-15 20:20:10 +04:00
|
|
|
Client_node_rpcs.Blocks.pending_operations
|
2017-04-05 01:35:41 +04:00
|
|
|
cctxt block >>=? fun (ops, pendings) ->
|
|
|
|
return (Operation_hash.Set.elements @@
|
2016-10-20 20:54:16 +04:00
|
|
|
Operation_hash.Set.union
|
|
|
|
(Prevalidation.preapply_result_operations ops)
|
|
|
|
pendings)
|
2017-04-05 01:35:41 +04:00
|
|
|
| Some operations -> return operations
|
|
|
|
end >>=? fun operations ->
|
2016-09-08 21:13:10 +04:00
|
|
|
begin
|
|
|
|
match priority with
|
2017-03-08 21:47:01 +04:00
|
|
|
| `Set prio -> begin
|
2017-02-15 20:20:10 +04:00
|
|
|
Client_proto_rpcs.Helpers.minimal_time
|
|
|
|
cctxt block ~prio () >>=? fun time ->
|
2017-03-14 19:32:01 +04:00
|
|
|
return (prio, time)
|
2016-09-08 21:13:10 +04:00
|
|
|
end
|
2017-03-08 21:47:01 +04:00
|
|
|
| `Auto (src_pkh, max_priority) ->
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_proto_rpcs.Helpers.Rights.mining_rights_for_delegate cctxt
|
2016-09-08 21:13:10 +04:00
|
|
|
?max_priority
|
|
|
|
~first_level:level
|
|
|
|
~last_level:level
|
|
|
|
block src_pkh () >>=? fun possibilities ->
|
|
|
|
try
|
|
|
|
let _, prio, time =
|
|
|
|
List.find (fun (l,_,_) -> l = level) possibilities in
|
|
|
|
return (prio, time)
|
|
|
|
with Not_found ->
|
2016-12-03 16:05:02 +04:00
|
|
|
Error_monad.failwith "No slot found at level %a" Raw_level.pp level
|
2016-09-08 21:13:10 +04:00
|
|
|
end >>=? fun (priority, minimal_timestamp) ->
|
|
|
|
lwt_log_info "Mining block at level %a prio %d"
|
|
|
|
Raw_level.pp level priority >>= fun () ->
|
|
|
|
begin
|
|
|
|
match timestamp, minimal_timestamp with
|
2017-03-14 19:32:01 +04:00
|
|
|
| None, timestamp -> return timestamp
|
|
|
|
| Some timestamp, minimal_timestamp ->
|
2016-09-08 21:13:10 +04:00
|
|
|
if timestamp < minimal_timestamp then
|
2016-12-03 16:05:02 +04:00
|
|
|
Error_monad.failwith
|
2016-09-08 21:13:10 +04:00
|
|
|
"Proposed timestamp %a is earlier than minimal timestamp %a"
|
|
|
|
Time.pp_hum timestamp
|
|
|
|
Time.pp_hum minimal_timestamp
|
|
|
|
else
|
2017-03-14 19:32:01 +04:00
|
|
|
return timestamp
|
2016-09-08 21:13:10 +04:00
|
|
|
end >>=? fun timestamp ->
|
|
|
|
let request = List.length operations in
|
2017-02-15 20:20:10 +04:00
|
|
|
Client_node_rpcs.Blocks.preapply
|
2017-03-14 19:32:01 +04:00
|
|
|
cctxt block ~timestamp ~sort operations >>=?
|
2016-09-08 21:13:10 +04:00
|
|
|
fun { operations ; fitness ; timestamp } ->
|
|
|
|
let valid = List.length operations.applied in
|
|
|
|
lwt_log_info "Found %d valid operations (%d refused) for timestamp %a"
|
|
|
|
valid (request - valid)
|
|
|
|
Time.pp_hum timestamp >>= fun () ->
|
|
|
|
lwt_log_info "Computed fitness %a" Fitness.pp fitness >>= fun () ->
|
|
|
|
if best_effort
|
2017-02-24 20:17:53 +04:00
|
|
|
|| ( Operation_hash.Map.is_empty operations.refused
|
|
|
|
&& Operation_hash.Map.is_empty operations.branch_refused
|
|
|
|
&& Operation_hash.Map.is_empty operations.branch_delayed ) then
|
2016-12-03 16:05:02 +04:00
|
|
|
inject_block cctxt ?force ~src_sk
|
2017-03-30 15:16:21 +04:00
|
|
|
~priority ~timestamp ~fitness ~seed_nonce block
|
|
|
|
[operations.applied]
|
2016-09-08 21:13:10 +04:00
|
|
|
else
|
|
|
|
failwith "Cannot (fully) validate the given operations."
|
|
|
|
|
|
|
|
|
|
|
|
(** Worker *)
|
|
|
|
|
|
|
|
module State : sig
|
|
|
|
|
|
|
|
val get_block:
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_commands.context ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Raw_level.t -> Block_hash.t list tzresult Lwt.t
|
|
|
|
|
|
|
|
val record_block:
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_commands.context ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Raw_level.t -> Block_hash.t -> Nonce.t -> unit tzresult Lwt.t
|
|
|
|
|
|
|
|
end = struct
|
|
|
|
|
|
|
|
module LevelMap = Map.Make(Raw_level)
|
|
|
|
|
|
|
|
type t = Block_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 "blocks" (list Block_hash.encoding))))
|
|
|
|
|
2017-03-15 04:17:20 +04:00
|
|
|
let filename cctxt =
|
|
|
|
Client_commands.(Filename.concat cctxt.config.base_dir "blocks")
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-03-15 04:17:20 +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
|
2016-12-01 21:27:53 +04:00
|
|
|
Data_encoding_ezjsonm.read_file filename >>= function
|
2017-01-23 14:09:45 +04:00
|
|
|
| Error _ ->
|
2016-09-08 21:13:10 +04:00
|
|
|
failwith "couldn't to read the block file"
|
2017-01-23 14:09:45 +04:00
|
|
|
| Ok json ->
|
2016-09-08 21:13:10 +04:00
|
|
|
match Data_encoding.Json.destruct encoding json with
|
|
|
|
| exception _ -> (* TODO print_error *)
|
|
|
|
failwith "didn't understand the block file"
|
|
|
|
| map ->
|
|
|
|
return map
|
|
|
|
|
2017-03-15 04:17:20 +04:00
|
|
|
let save cctxt map =
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.catch
|
|
|
|
(fun () ->
|
2017-03-15 04:17:20 +04:00
|
|
|
let dirname = Client_commands.(cctxt.config.base_dir) in
|
2016-12-01 21:27:53 +04:00
|
|
|
(if not (Sys.file_exists dirname) then Lwt_utils.create_dir dirname
|
2016-09-08 21:13:10 +04:00
|
|
|
else Lwt.return ()) >>= fun () ->
|
2017-03-15 04:17:20 +04:00
|
|
|
let filename = filename cctxt in
|
2016-09-08 21:13:10 +04:00
|
|
|
let json = Data_encoding.Json.construct encoding map in
|
2016-12-01 21:27:53 +04:00
|
|
|
Data_encoding_ezjsonm.write_file filename json >>= function
|
2017-01-23 14:09:45 +04:00
|
|
|
| Error _ -> failwith "Json.write_file"
|
|
|
|
| Ok () -> return ())
|
2016-09-08 21:13:10 +04:00
|
|
|
(fun exn ->
|
2016-12-03 16:05:02 +04:00
|
|
|
Error_monad.failwith
|
2016-09-08 21:13:10 +04:00
|
|
|
"could not write the block file: %s."
|
|
|
|
(Printexc.to_string exn))
|
|
|
|
|
|
|
|
let lock = Lwt_mutex.create ()
|
|
|
|
|
2017-03-15 04:17:20 +04:00
|
|
|
let get_block cctxt level =
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt_mutex.with_lock lock
|
|
|
|
(fun () ->
|
2017-03-15 04:17:20 +04:00
|
|
|
load cctxt >>=? fun map ->
|
2016-09-08 21:13:10 +04:00
|
|
|
try
|
|
|
|
let blocks = LevelMap.find level map in
|
|
|
|
return blocks
|
|
|
|
with Not_found -> return [])
|
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let record_block cctxt level hash nonce =
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt_mutex.with_lock lock
|
|
|
|
(fun () ->
|
2017-03-15 04:17:20 +04:00
|
|
|
load cctxt >>=? fun map ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let previous =
|
|
|
|
try LevelMap.find level map
|
|
|
|
with Not_found -> [] in
|
2017-03-15 04:17:20 +04:00
|
|
|
save cctxt
|
2016-09-08 21:13:10 +04:00
|
|
|
(LevelMap.add level (hash :: previous) map)) >>=? fun () ->
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_proto_nonces.add cctxt hash nonce
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
end
|
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let get_mining_slot cctxt
|
2016-09-08 21:13:10 +04:00
|
|
|
?max_priority (bi: Client_mining_blocks.block_info) delegates =
|
|
|
|
let block = `Hash bi.hash in
|
|
|
|
let level = Raw_level.succ bi.level.level in
|
|
|
|
Lwt_list.filter_map_p
|
|
|
|
(fun delegate ->
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_proto_rpcs.Helpers.Rights.mining_rights_for_delegate cctxt
|
2016-09-08 21:13:10 +04:00
|
|
|
?max_priority
|
|
|
|
~first_level:level
|
|
|
|
~last_level:level
|
|
|
|
block delegate () >>= function
|
|
|
|
| Error errs ->
|
|
|
|
log_error "Error while fetching mining possibilities:\n%a"
|
|
|
|
pp_print_error errs ;
|
|
|
|
Lwt.return_none
|
|
|
|
| Ok slots ->
|
2017-03-14 19:32:01 +04:00
|
|
|
let convert = fun (_lvl, slot, timestamp) ->
|
|
|
|
(timestamp, (bi, slot, delegate)) in
|
|
|
|
Lwt.return (Some (List.map convert slots)))
|
2016-09-08 21:13:10 +04:00
|
|
|
delegates >>= fun slots ->
|
|
|
|
let sorted_slots =
|
2017-02-15 20:20:10 +04:00
|
|
|
List.sort
|
|
|
|
(fun (t1,_) (t2,_) -> Time.compare t1 t2)
|
|
|
|
(List.flatten slots) in
|
2016-09-08 21:13:10 +04:00
|
|
|
match sorted_slots with
|
|
|
|
| [] -> Lwt.return None
|
|
|
|
| slot :: _ -> Lwt.return (Some slot)
|
|
|
|
|
|
|
|
let rec insert_mining_slot slot = function
|
|
|
|
| [] -> [slot]
|
2017-02-15 20:20:10 +04:00
|
|
|
| ((timestamp,_) :: _) as slots when Time.(fst slot < timestamp) ->
|
|
|
|
slot :: slots
|
2016-09-08 21:13:10 +04:00
|
|
|
| slot' :: slots -> slot' :: insert_mining_slot slot slots
|
|
|
|
|
|
|
|
type state = {
|
|
|
|
genesis: Block_hash.t ;
|
|
|
|
delegates: public_key_hash list ;
|
2017-02-15 19:37:29 +04:00
|
|
|
mutable best: Client_mining_blocks.block_info ;
|
2016-09-08 21:13:10 +04:00
|
|
|
mutable future_slots:
|
|
|
|
(Time.t * (Client_mining_blocks.block_info * int * public_key_hash)) list ;
|
|
|
|
}
|
|
|
|
|
2017-02-15 19:37:29 +04:00
|
|
|
let create_state genesis delegates best =
|
2016-09-08 21:13:10 +04:00
|
|
|
{ genesis ;
|
|
|
|
delegates ;
|
2017-02-15 19:37:29 +04:00
|
|
|
best ;
|
2016-09-08 21:13:10 +04:00
|
|
|
future_slots = [] ;
|
|
|
|
}
|
|
|
|
|
2017-02-15 19:37:29 +04:00
|
|
|
let drop_old_slots ~before state =
|
|
|
|
state.future_slots <-
|
|
|
|
List.filter
|
2017-02-15 23:39:38 +04:00
|
|
|
(fun (t, _slot) -> Time.compare before t <= 0)
|
2017-02-15 19:37:29 +04:00
|
|
|
state.future_slots
|
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
let compute_timeout { future_slots } =
|
|
|
|
match future_slots with
|
|
|
|
| [] ->
|
|
|
|
Lwt_utils.never_ending
|
|
|
|
| (timestamp, _) :: _ ->
|
|
|
|
let now = Time.now () in
|
|
|
|
let delay = Time.diff timestamp now in
|
|
|
|
if delay <= 0L then
|
2017-02-15 19:37:29 +04:00
|
|
|
if delay <= -1800L then
|
|
|
|
Lwt_unix.sleep 10.
|
|
|
|
else
|
|
|
|
Lwt.return_unit
|
2016-09-08 21:13:10 +04:00
|
|
|
else
|
|
|
|
Lwt_unix.sleep (Int64.to_float delay)
|
|
|
|
|
2017-02-14 13:33:34 +04:00
|
|
|
let get_unrevealed_nonces cctxt ?(force = false) block =
|
2017-04-05 01:35:41 +04:00
|
|
|
Client_proto_rpcs.Context.next_level cctxt.rpc_config block >>=? fun level ->
|
2017-02-14 13:33:34 +04:00
|
|
|
let cur_cycle = level.cycle in
|
|
|
|
match Cycle.pred cur_cycle with
|
|
|
|
| None -> return []
|
|
|
|
| Some cycle ->
|
|
|
|
Client_mining_blocks.blocks_from_cycle
|
2017-04-05 01:35:41 +04:00
|
|
|
cctxt.rpc_config block cycle >>=? fun blocks ->
|
2017-02-16 00:18:48 +04:00
|
|
|
map_filter_s (fun hash ->
|
|
|
|
Client_proto_nonces.find cctxt hash >>= function
|
2017-02-14 13:33:34 +04:00
|
|
|
| None -> return None
|
|
|
|
| Some nonce ->
|
2017-02-16 00:18:48 +04:00
|
|
|
Client_proto_rpcs.Context.level
|
2017-04-05 01:35:41 +04:00
|
|
|
cctxt.rpc_config (`Hash hash) >>=? fun level ->
|
2017-02-14 13:33:34 +04:00
|
|
|
if force then
|
2017-02-16 00:18:48 +04:00
|
|
|
return (Some (hash, (level.level, nonce)))
|
2017-02-14 13:33:34 +04:00
|
|
|
else
|
|
|
|
Client_proto_rpcs.Context.Nonce.get
|
2017-04-05 01:35:41 +04:00
|
|
|
cctxt.rpc_config block level.level >>=? function
|
2017-02-14 13:33:34 +04:00
|
|
|
| Missing nonce_hash
|
|
|
|
when Nonce.check_hash nonce nonce_hash ->
|
|
|
|
cctxt.warning "Found nonce for %a (level: %a)@."
|
2017-02-16 00:18:48 +04:00
|
|
|
Block_hash.pp_short hash
|
|
|
|
Level.pp level >>= fun () ->
|
|
|
|
return (Some (hash, (level.level, nonce)))
|
2017-02-14 13:33:34 +04:00
|
|
|
| Missing _nonce_hash ->
|
|
|
|
cctxt.error "Incoherent nonce for level %a"
|
2017-02-16 00:18:48 +04:00
|
|
|
Raw_level.pp level.level >>= fun () ->
|
2017-02-14 13:33:34 +04:00
|
|
|
return None
|
|
|
|
| Forgotten -> return None
|
|
|
|
| Revealed _ -> return None)
|
2017-02-16 00:18:48 +04:00
|
|
|
blocks
|
2017-02-14 13:33:34 +04:00
|
|
|
|
2017-02-28 11:18:06 +04:00
|
|
|
let safe_get_unrevealed_nonces cctxt block =
|
|
|
|
get_unrevealed_nonces cctxt block >>= function
|
|
|
|
| Ok r -> Lwt.return r
|
|
|
|
| Error err ->
|
|
|
|
lwt_warn "Cannot read nonces: %a@." pp_print_error err >>= fun () ->
|
|
|
|
Lwt.return []
|
|
|
|
|
|
|
|
|
|
|
|
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-14 13:33:34 +04:00
|
|
|
let insert_block
|
|
|
|
cctxt ?max_priority state (bi: Client_mining_blocks.block_info) =
|
|
|
|
begin
|
2017-02-28 11:18:06 +04:00
|
|
|
safe_get_unrevealed_nonces cctxt (`Hash bi.hash) >>= fun nonces ->
|
2017-02-14 13:33:34 +04:00
|
|
|
Client_mining_revelation.forge_seed_nonce_revelation
|
|
|
|
cctxt ~force:true (`Hash bi.hash) (List.map snd nonces)
|
|
|
|
end >>= fun _ignore_error ->
|
2017-02-15 23:39:38 +04:00
|
|
|
if Fitness.compare state.best.fitness bi.fitness < 0 then begin
|
2017-02-15 19:37:29 +04:00
|
|
|
state.best <- bi ;
|
2017-02-15 23:39:38 +04:00
|
|
|
drop_old_slots
|
|
|
|
~before:(Time.add state.best.timestamp (-1800L)) state ;
|
|
|
|
end ;
|
2017-04-05 03:02:10 +04:00
|
|
|
get_delegates cctxt state >>=? fun delegates ->
|
2017-04-05 01:35:41 +04:00
|
|
|
get_mining_slot cctxt.rpc_config ?max_priority bi delegates >>= function
|
2016-09-08 21:13:10 +04:00
|
|
|
| None ->
|
|
|
|
lwt_debug
|
|
|
|
"Can't compute slot for %a" Block_hash.pp_short bi.hash >>= fun () ->
|
2017-04-05 03:02:10 +04:00
|
|
|
return ()
|
2016-09-08 21:13:10 +04:00
|
|
|
| Some ((timestamp, (_,_,delegate)) as slot) ->
|
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 "New mining slot at %a for %s after %a"
|
|
|
|
Time.pp_hum timestamp
|
|
|
|
name
|
|
|
|
Block_hash.pp_short bi.hash >>= fun () ->
|
|
|
|
state.future_slots <- insert_mining_slot slot state.future_slots ;
|
2017-04-05 03:02:10 +04:00
|
|
|
return ()
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let pop_mining_slots state =
|
|
|
|
let now = Time.now () in
|
|
|
|
let rec pop acc = function
|
|
|
|
| [] -> List.rev acc, []
|
|
|
|
| ((timestamp,_) :: _) as slots when Time.compare now timestamp < 0 ->
|
|
|
|
List.rev acc, slots
|
|
|
|
| slot :: slots -> pop (slot :: acc) slots in
|
|
|
|
let slots, future_slots = pop [] state.future_slots in
|
|
|
|
state.future_slots <- future_slots ;
|
|
|
|
slots
|
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let insert_blocks cctxt ?max_priority state bis =
|
2017-04-05 03:02:10 +04:00
|
|
|
iter_s (insert_block cctxt ?max_priority state) bis >>= function
|
|
|
|
| Ok () ->
|
|
|
|
Lwt.return_unit
|
|
|
|
| Error err ->
|
|
|
|
Format.eprintf "Error: %a" pp_print_error err ;
|
|
|
|
Lwt.return_unit
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let mine cctxt state =
|
2016-09-08 21:13:10 +04:00
|
|
|
let slots = pop_mining_slots state in
|
2017-04-05 01:35:41 +04:00
|
|
|
map_p
|
2016-09-08 21:13:10 +04:00
|
|
|
(fun (timestamp, (bi, prio, delegate)) ->
|
|
|
|
let block = `Hash bi.Client_mining_blocks.hash in
|
|
|
|
let timestamp =
|
|
|
|
if Block_hash.equal bi.Client_mining_blocks.hash state.genesis then
|
|
|
|
Time.now ()
|
|
|
|
else
|
|
|
|
timestamp in
|
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_debug "Try mining after %a (slot %d) for %s (%a)"
|
|
|
|
Block_hash.pp_short bi.hash
|
|
|
|
prio name Time.pp_hum timestamp >>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Client_node_rpcs.Blocks.pending_operations cctxt.rpc_config
|
|
|
|
block >>=? fun (res, ops) ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let operations =
|
2017-02-24 20:17:53 +04:00
|
|
|
let open Operation_hash.Set in
|
2016-10-20 20:54:16 +04:00
|
|
|
elements (union ops (Prevalidation.preapply_result_operations res)) in
|
2016-09-08 21:13:10 +04:00
|
|
|
let request = List.length operations in
|
2017-04-05 01:35:41 +04:00
|
|
|
Client_node_rpcs.Blocks.preapply cctxt.rpc_config block
|
2016-09-08 21:13:10 +04:00
|
|
|
~timestamp ~sort:true operations >>= function
|
|
|
|
| Error errs ->
|
|
|
|
lwt_log_error "Error while prevalidating operations:\n%a"
|
|
|
|
pp_print_error
|
|
|
|
errs >>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
return None
|
2016-09-08 21:13:10 +04:00
|
|
|
| Ok { operations ; fitness ; timestamp } ->
|
|
|
|
lwt_debug
|
|
|
|
"Computed condidate block after %a (slot %d): %d/%d fitness: %a"
|
|
|
|
Block_hash.pp_short bi.hash prio
|
|
|
|
(List.length operations.applied) request
|
|
|
|
Fitness.pp fitness
|
|
|
|
>>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
return
|
2016-09-08 21:13:10 +04:00
|
|
|
(Some (bi, prio, fitness, timestamp, operations, delegate)))
|
2017-04-05 01:35:41 +04:00
|
|
|
slots >>=? fun candidates ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let candidates =
|
|
|
|
List.sort
|
|
|
|
(fun (_,_,f1,_,_,_) (_,_,f2,_,_,_) -> ~- (Fitness.compare f1 f2))
|
|
|
|
(Utils.unopt_list candidates) in
|
|
|
|
match candidates with
|
|
|
|
| (bi, priority, fitness, timestamp, operations, delegate) :: _
|
2017-02-15 19:37:29 +04:00
|
|
|
when Fitness.compare state.best.fitness fitness < 0 -> begin
|
2016-09-08 21:13:10 +04:00
|
|
|
let level = Raw_level.succ bi.level.level in
|
2017-04-05 03:02:10 +04:00
|
|
|
cctxt.message
|
2016-09-08 21:13:10 +04:00
|
|
|
"Select candidate block after %a (slot %d) fitness: %a"
|
|
|
|
Block_hash.pp_short bi.hash priority
|
|
|
|
Fitness.pp fitness >>= fun () ->
|
|
|
|
let seed_nonce = generate_seed_nonce () in
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_keys.get_key cctxt delegate >>=? fun (_,_,src_sk) ->
|
2017-04-05 01:35:41 +04:00
|
|
|
inject_block cctxt.rpc_config
|
2017-03-30 15:16:21 +04:00
|
|
|
~force:true ~src_sk ~priority ~timestamp ~fitness ~seed_nonce
|
|
|
|
(`Hash bi.hash) [operations.applied]
|
2016-09-08 21:13:10 +04:00
|
|
|
|> trace_exn (Failure "Error while injecting block") >>=? fun block_hash ->
|
2016-12-03 16:05:02 +04:00
|
|
|
State.record_block cctxt level block_hash seed_nonce
|
2016-09-08 21:13:10 +04:00
|
|
|
|> trace_exn (Failure "Error while recording block") >>=? fun () ->
|
2017-04-05 03:02:10 +04:00
|
|
|
Client_keys.Public_key_hash.name cctxt delegate >>=? fun name ->
|
2016-12-03 16:05:02 +04:00
|
|
|
cctxt.message
|
2016-09-08 21:13:10 +04:00
|
|
|
"Injected block %a for %s after %a \
|
|
|
|
\ (level %a, slot %d, fitness %a, operations %d)"
|
|
|
|
Block_hash.pp_short block_hash
|
|
|
|
name
|
|
|
|
Block_hash.pp_short bi.hash
|
|
|
|
Raw_level.pp level priority
|
|
|
|
Fitness.pp fitness
|
2016-11-22 20:59:09 +04:00
|
|
|
(List.length operations.applied) >>= fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
return ()
|
|
|
|
end
|
|
|
|
| _ ->
|
|
|
|
lwt_debug "No valid candidates." >>= fun () ->
|
|
|
|
return ()
|
|
|
|
|
2017-02-15 20:20:10 +04:00
|
|
|
let create
|
|
|
|
cctxt ?max_priority delegates
|
|
|
|
(block_stream:
|
2017-04-05 01:35:41 +04:00
|
|
|
Client_mining_blocks.block_info list tzresult Lwt_stream.t)
|
2017-02-15 20:20:10 +04:00
|
|
|
(endorsement_stream:
|
2017-04-05 01:35:41 +04:00
|
|
|
Client_mining_operations.valid_endorsement tzresult Lwt_stream.t) =
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt_stream.get block_stream >>= function
|
2017-04-05 01:35:41 +04:00
|
|
|
| None | Some (Ok [] | Error _) ->
|
2016-12-03 16:05:02 +04:00
|
|
|
cctxt.Client_commands.error "Can't fetch the current block head."
|
2017-04-05 01:35:41 +04:00
|
|
|
| Some (Ok (bi :: _ as initial_heads)) ->
|
|
|
|
Client_node_rpcs.Blocks.hash cctxt.rpc_config `Genesis >>=? fun genesis_hash ->
|
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 last_get_endorsement = ref None in
|
|
|
|
let get_endorsement () =
|
|
|
|
match !last_get_endorsement with
|
|
|
|
| None ->
|
|
|
|
let t = Lwt_stream.get endorsement_stream in
|
|
|
|
last_get_endorsement := Some t ;
|
|
|
|
t
|
|
|
|
| Some t -> t in
|
2017-02-15 19:37:29 +04:00
|
|
|
let state = create_state genesis_hash delegates bi in
|
2016-12-03 16:05:02 +04:00
|
|
|
insert_blocks cctxt ?max_priority state initial_heads >>= fun () ->
|
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) ;
|
|
|
|
(get_endorsement () >|= fun e -> `Endorsement e) ;
|
|
|
|
] >>= function
|
2017-04-05 01:35:41 +04:00
|
|
|
| `Hash (None | Some (Error _))
|
|
|
|
| `Endorsement (None | Some (Error _)) ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.return_unit
|
2017-04-05 01:35:41 +04:00
|
|
|
| `Hash (Some (Ok bis)) -> begin
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.cancel timeout ;
|
|
|
|
last_get_block := None ;
|
|
|
|
lwt_debug
|
|
|
|
"@[<hov 2>Discoverer blocks:@ %a@]"
|
|
|
|
(Format.pp_print_list
|
|
|
|
(fun ppf bi ->
|
|
|
|
Block_hash.pp_short ppf bi.Client_mining_blocks.hash))
|
|
|
|
bis
|
|
|
|
>>= fun () ->
|
2016-12-03 16:05:02 +04:00
|
|
|
insert_blocks cctxt ?max_priority state bis >>= fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
worker_loop ()
|
|
|
|
end
|
2017-04-05 01:35:41 +04:00
|
|
|
| `Endorsement (Some (Ok e)) ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.cancel timeout ;
|
|
|
|
last_get_endorsement := None ;
|
2016-12-03 16:05:02 +04:00
|
|
|
Client_keys.Public_key_hash.name cctxt
|
2016-09-08 21:13:10 +04:00
|
|
|
e.Client_mining_operations.source >>= fun _source ->
|
|
|
|
(* TODO *)
|
|
|
|
worker_loop ()
|
|
|
|
| `Timeout ->
|
|
|
|
lwt_debug "Waking up for mining..." >>= fun () ->
|
|
|
|
begin
|
2016-12-03 16:05:02 +04:00
|
|
|
mine cctxt state >>= function
|
2016-09-08 21:13:10 +04:00
|
|
|
| Ok () -> Lwt.return_unit
|
|
|
|
| Error errs ->
|
|
|
|
lwt_log_error "Error while mining:\n%a"
|
|
|
|
pp_print_error
|
|
|
|
errs >>= fun () ->
|
|
|
|
Lwt.return_unit
|
|
|
|
end >>= fun () ->
|
|
|
|
worker_loop () in
|
|
|
|
lwt_log_info "Starting mining daemon" >>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
worker_loop () >>= fun () ->
|
|
|
|
return ()
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
(* FIXME bug in ocamldep ?? *)
|
|
|
|
open Level
|