Alpha/Baker: forge a block locally

This commit is contained in:
Vincent Botbol 2018-06-29 01:16:26 +02:00 committed by Benjamin Canou
parent d8805ede7b
commit 83f4a162dc
6 changed files with 130 additions and 93 deletions

View File

@ -15,6 +15,7 @@ module Proto = Tezos_protocol_alpha.Functor.Make(Alpha_environment)
module Alpha_block_services = Block_services.Make(Proto)(Proto) module Alpha_block_services = Block_services.Make(Proto)(Proto)
include Proto include Proto
module LiftedMain = Alpha_environment.Lift(Proto)
class type rpc_context = object class type rpc_context = object
inherit RPC_context.json inherit RPC_context.json

View File

@ -18,6 +18,7 @@ type block_info = {
timestamp: Time.t ; timestamp: Time.t ;
protocol: Protocol_hash.t ; protocol: Protocol_hash.t ;
next_protocol: Protocol_hash.t ; next_protocol: Protocol_hash.t ;
proto_level: int ;
level: Raw_level.t ; level: Raw_level.t ;
context : Context_hash.t ; context : Context_hash.t ;
} }
@ -29,12 +30,12 @@ let raw_info cctxt ?(chain = `Main) hash shell_header =
cctxt ~chain ~block () >>=? fun { current_protocol = protocol ; cctxt ~chain ~block () >>=? fun { current_protocol = protocol ;
next_protocol } -> next_protocol } ->
let { Tezos_base.Block_header.predecessor ; fitness ; let { Tezos_base.Block_header.predecessor ; fitness ;
timestamp ; level ; context ; _ } = timestamp ; level ; context ; proto_level ; _ } =
shell_header in shell_header in
match Raw_level.of_int32 level with match Raw_level.of_int32 level with
| Ok level -> | Ok level ->
return { hash ; chain_id ; predecessor ; fitness ; return { hash ; chain_id ; predecessor ; fitness ;
timestamp ; protocol ; next_protocol ; level ; context } timestamp ; protocol ; next_protocol ; proto_level ; level ; context }
| Error _ -> | Error _ ->
failwith "Cannot convert level into int32" failwith "Cannot convert level into int32"

View File

@ -18,6 +18,7 @@ type block_info = {
timestamp: Time.t ; timestamp: Time.t ;
protocol: Protocol_hash.t ; protocol: Protocol_hash.t ;
next_protocol: Protocol_hash.t ; next_protocol: Protocol_hash.t ;
proto_level: int ;
level: Raw_level.t ; level: Raw_level.t ;
context : Context_hash.t ; context : Context_hash.t ;
} }

View File

@ -13,7 +13,6 @@ open Alpha_context
include Tezos_stdlib.Logging.Make_semantic(struct let name = "client.baking" end) include Tezos_stdlib.Logging.Make_semantic(struct let name = "client.baking" end)
open Logging open Logging
(* The index of the different components of the protocol's validation passes *) (* The index of the different components of the protocol's validation passes *)
(* TODO: ideally, we would like this to be more abstract and possibly part of (* TODO: ideally, we would like this to be more abstract and possibly part of
the protocol, while retaining the generality of lists *) the protocol, while retaining the generality of lists *)
@ -175,18 +174,18 @@ let retain_operations_up_to_quota operations max_quota =
List.rev operations List.rev operations
let classify_operations ?threshold (ops: Proto_alpha.operation list) = let classify_operations ?threshold (ops: Proto_alpha.operation list) =
let t = Array.make (List.length Proto_alpha.Main.validation_passes) [] in let t = Array.make (List.length LiftedMain.validation_passes) [] in
List.iter List.iter
(fun (op: Proto_alpha.operation) -> (fun (op: Proto_alpha.operation) ->
List.iter List.iter
(fun pass -> t.(pass) <- op :: t.(pass)) (fun pass -> t.(pass) <- op :: t.(pass))
(Proto_alpha.Main.acceptable_passes op)) (Main.acceptable_passes op))
ops ; ops ;
let t = Array.map List.rev t in let t = Array.map List.rev t in
(* Retrieve the maximum paying manager operations *) (* Retrieve the maximum paying manager operations *)
let manager_operations = t.(managers_index) in let manager_operations = t.(managers_index) in
let { Alpha_environment.Updater.max_size } = let { Alpha_environment.Updater.max_size } =
List.nth Proto_alpha.Main.validation_passes managers_index in List.nth LiftedMain.validation_passes managers_index in
sort_operations_by_fee ?threshold manager_operations >>=? fun ordered_operations -> sort_operations_by_fee ?threshold manager_operations >>=? fun ordered_operations ->
let max_operations = let max_operations =
retain_operations_up_to_quota ordered_operations max_size retain_operations_up_to_quota ordered_operations max_size
@ -301,7 +300,6 @@ let error_of_op (result: error Preapply_result.t) op =
try Some (Failed_to_preapply (op, snd @@ Operation_hash.Map.find h result.branch_delayed)) try Some (Failed_to_preapply (op, snd @@ Operation_hash.Map.find h result.branch_delayed))
with Not_found -> None with Not_found -> None
let forge_block cctxt ?(chain = `Main) block let forge_block cctxt ?(chain = `Main) block
?threshold ?threshold
?force ?force
@ -514,13 +512,18 @@ let pop_baking_slots state =
state.future_slots <- future_slots ; state.future_slots <- future_slots ;
slots slots
let filter_invalid_operations (cctxt : #full) state block_info (operations : packed_operation list list) = let filter_and_apply_operations
state
block_info
~timestamp
?protocol_data
(operations : packed_operation list list) =
let open Client_baking_simulator in let open Client_baking_simulator in
lwt_debug Tag.DSL.(fun f -> lwt_debug Tag.DSL.(fun f ->
f "Starting client-side validation %a" f "Starting client-side validation %a"
-% t event "baking_local_validation_start" -% t event "baking_local_validation_start"
-% a Block_hash.Logging.tag block_info.Client_baking_blocks.hash) >>= fun () -> -% a Block_hash.Logging.tag block_info.Client_baking_blocks.hash) >>= fun () ->
begin begin_construction cctxt state.index block_info >>= function begin begin_construction ~timestamp ?protocol_data state.index block_info >>= function
| Ok inc -> return inc | Ok inc -> return inc
| Error errs -> | Error errs ->
lwt_log_error Tag.DSL.(fun f -> lwt_log_error Tag.DSL.(fun f ->
@ -529,8 +532,8 @@ let filter_invalid_operations (cctxt : #full) state block_info (operations : pac
-% a errs_tag errs) >>= fun () -> -% a errs_tag errs) >>= fun () ->
lwt_log_notice Tag.DSL.(fun f -> f "Retrying to open the context" -% t event "reopen_context") >>= fun () -> lwt_log_notice Tag.DSL.(fun f -> f "Retrying to open the context" -% t event "reopen_context") >>= fun () ->
Client_baking_simulator.load_context ~context_path:state.context_path >>= fun index -> Client_baking_simulator.load_context ~context_path:state.context_path >>= fun index ->
begin_construction cctxt index block_info >>=? fun inc -> begin_construction ~timestamp ?protocol_data index block_info >>=? fun inc ->
state.index <- index; state.index <- index ;
return inc return inc
end >>=? fun initial_inc -> end >>=? fun initial_inc ->
let endorsements = List.nth operations endorsements_index in let endorsements = List.nth operations endorsements_index in
@ -570,37 +573,89 @@ let filter_invalid_operations (cctxt : #full) state block_info (operations : pac
filter_valid_operations inc managers >>=? fun (inc, managers) -> filter_valid_operations inc managers >>=? fun (inc, managers) ->
(* Gives a chance to the endorser to fund their deposit in the current block *) (* Gives a chance to the endorser to fund their deposit in the current block *)
filter_map_s (is_valid_endorsement inc) endorsements >>=? fun endorsements -> filter_map_s (is_valid_endorsement inc) endorsements >>=? fun endorsements ->
finalize_construction inc >>= function finalize_construction inc >>=? fun _ ->
let quota : Alpha_environment.Updater.quota list = LiftedMain.validation_passes in
(* This shouldn't happen *)
tzforce state.constants >>=? fun constants ->
let endorsements =
List.sub (List.rev endorsements)
constants.Constants.parametric.endorsers_per_block in
let votes =
retain_operations_up_to_quota
(List.rev votes)
(List.nth quota votes_index).max_size in
let anonymous =
retain_operations_up_to_quota
(List.rev anonymous)
(List.nth quota anonymous_index).max_size in
(* manager operations size check already occured in classify operations *)
let operations = List.map List.rev [ endorsements ; votes ; anonymous ; managers ] in
(* Re-run with the final operations *)
fold_left_s
add_operation
initial_inc (List.flatten operations) >>=? fun inc ->
finalize_construction inc >>=? fun (validation_result, metadata) ->
return @@ (inc, (validation_result, metadata), operations)
(* Build the block header : mimics node prevalidation *)
let finalize_block_header
(inc : Client_baking_simulator.incremental)
~timestamp
(validation_result, _metadata)
operations =
let { T.context ; fitness ; message ; _ } = validation_result in
let validation_passes = List.length LiftedMain.validation_passes in
let operations_hash : Operation_list_list_hash.t =
Operation_list_list_hash.compute
(List.map
(fun sl ->
Operation_list_hash.compute
(List.map Operation.hash_packed sl)
) operations
) in
Context.hash ~time:timestamp ?message context >>= fun context ->
let header =
{ inc.header with
level = Raw_level.to_int32 (Raw_level.succ inc.predecessor.level) ;
validation_passes ;
operations_hash ;
fitness ;
context ;
} in
return header
let shell_prevalidation
(cctxt : #Proto_alpha.full)
~chain
~block
seed_nonce_hash
operations
(timestamp, (bi, priority, delegate)) =
let protocol_data =
forge_faked_protocol_data ~priority ~seed_nonce_hash in
Alpha_block_services.Helpers.Preapply.block
cctxt ~chain ~block
~timestamp ~sort:true ~protocol_data operations
>>= function
| Error errs -> | Error errs ->
lwt_log_error Tag.DSL.(fun f -> lwt_log_error Tag.DSL.(fun f ->
f "Client-side validation: invalid block built. Building an empty block...\n%a" f "Shell-side validation: error while prevalidating operations:@\n%a"
-% t event "built_invalid_block_error" -% t event "built_invalid_block_error"
-% a errs_tag errs) >>= fun () -> -% a errs_tag errs) >>= fun () ->
return [ [] ; [] ; [] ; [] ] return None
| Ok () -> | Ok (shell_header, operations) ->
let quota : Alpha_environment.Updater.quota list = Main.validation_passes in let raw_ops =
(* This shouldn't happen *) List.map (fun l ->
tzforce state.constants >>=? fun constants -> List.map snd l.Preapply_result.applied) operations in
let endorsements = return
List.sub (List.rev endorsements) constants.Constants.parametric.endorsers_per_block (Some (bi, priority, shell_header, raw_ops, delegate, seed_nonce_hash))
in
let votes =
retain_operations_up_to_quota
(List.rev votes)
(List.nth quota votes_index).max_size in
let anonymous =
retain_operations_up_to_quota
(List.rev anonymous)
(List.nth quota anonymous_index).max_size in
(* manager operations size check already occured in classify operations *)
return @@ List.map List.rev [ endorsements ; votes ; anonymous ; managers ]
let bake_slot let bake_slot
cctxt cctxt
state state
?threshold ?threshold
seed_nonce_hash seed_nonce_hash
(timestamp, (bi, priority, delegate)) (* baking slot *) ((timestamp, (bi, priority, delegate)) as slot)
= =
let chain = `Hash bi.Client_baking_blocks.chain_id in let chain = `Hash bi.Client_baking_blocks.chain_id in
let block = `Hash (bi.hash, 0) in let block = `Hash (bi.hash, 0) in
@ -619,55 +674,39 @@ let bake_slot
-% s bake_priorty_tag priority -% s bake_priorty_tag priority
-% s Client_keys.Logging.tag name -% s Client_keys.Logging.tag name
-% a timestamp_tag timestamp) >>= fun () -> -% a timestamp_tag timestamp) >>= fun () ->
(* get and process operations *) (* Retrieve pending operations *)
Alpha_block_services.Mempool.pending_operations cctxt ~chain () >>=? fun mpool -> Alpha_block_services.Mempool.pending_operations cctxt ~chain () >>=? fun mpool ->
let operations = ops_of_mempool mpool in let operations = ops_of_mempool mpool in
let total_op_count = List.length operations in
let seed_nonce_hash = let seed_nonce_hash =
if next_level.expected_commitment then if next_level.expected_commitment then
Some seed_nonce_hash Some seed_nonce_hash
else else
None in None in
let protocol_data =
forge_faked_protocol_data ~priority ~seed_nonce_hash in
classify_operations ?threshold operations >>=? fun operations -> classify_operations ?threshold operations >>=? fun operations ->
begin (* Don't load an alpha context if the chain is still in genesis *)
(* Don't load an alpha context if the chain is still in genesis *) if Protocol_hash.(Proto_alpha.hash <> bi.next_protocol) then
if Protocol_hash.(bi.protocol = bi.next_protocol) then (* Delegate validation to shell *)
filter_invalid_operations cctxt state bi operations shell_prevalidation cctxt ~chain ~block seed_nonce_hash operations slot
else else
return operations let protocol_data = forge_faked_protocol_data ~priority ~seed_nonce_hash in
end >>= function filter_and_apply_operations ~timestamp ~protocol_data state bi operations >>= function
| Error errs -> | Error errs ->
lwt_log_error Tag.DSL.(fun f -> lwt_log_error Tag.DSL.(fun f ->
f "Client-side validation: error while filtering invalid operations :@\n%a" f "Client-side validation: error while filtering invalid operations :@\n%a"
-% t event "client_side_validation_error" -% t event "client_side_validation_error"
-% a errs_tag errs) >>= fun () -> -% a errs_tag errs) >>= fun () ->
return_none shell_prevalidation cctxt ~chain ~block seed_nonce_hash [] slot
| Ok operations -> | Ok (final_context, validation_result, operations) ->
Alpha_block_services.Helpers.Preapply.block lwt_debug Tag.DSL.(fun f ->
cctxt ~chain ~block f "Try forging locally the block header for %a (slot %d) for %s (%a)"
~timestamp ~sort:true ~protocol_data operations -% t event "try_forging"
>>= function -% a Block_hash.Logging.tag bi.hash
| Error errs -> -% s bake_priorty_tag priority
lwt_log_error Tag.DSL.(fun f -> -% s Client_keys.Logging.tag name
f "Error while prevalidating operations:@\n%a" -% a timestamp_tag timestamp) >>= fun () ->
-% t event "prevalidate_operations_error" finalize_block_header final_context ~timestamp validation_result operations >>=? fun shell_header ->
-% a errs_tag errs) >>= fun () -> let raw_ops = List.map (List.map forge) operations in
return_none return (Some (bi, priority, shell_header, raw_ops, delegate, seed_nonce_hash))
| Ok (shell_header, operations) ->
lwt_debug Tag.DSL.(fun f ->
f "Computed candidate block after %a (slot %d): %a/%d fitness: %a"
-% t event "candidate_block"
-% a Block_hash.Logging.tag bi.hash
-% s bake_priorty_tag priority
-% a operations_tag operations
-% s bake_op_count_tag total_op_count
-% a fitness_tag shell_header.fitness) >>= fun () ->
let operations =
List.map (fun l -> List.map snd l.Preapply_result.applied) operations in
return
(Some (bi, priority, shell_header, operations, delegate, seed_nonce_hash))
let fittest let fittest
(_, _, (h1: Block_header.shell_header), _, _, _) (_, _, (h1: Block_header.shell_header), _, _, _)
@ -764,8 +803,6 @@ let bake
f "No valid candidates." -% t event "no_baking_candidates") >>= fun () -> f "No valid candidates." -% t event "no_baking_candidates") >>= fun () ->
return_unit return_unit
(* [create] starts the main loop of the baker. The loop monitors new blocks and (* [create] starts the main loop of the baker. The loop monitors new blocks and
starts individual baking operations when baking-slots are available to any of starts individual baking operations when baking-slots are available to any of
the [delegates] *) the [delegates] *)

View File

@ -10,8 +10,6 @@
open Proto_alpha open Proto_alpha
open Alpha_context open Alpha_context
module Main = Alpha_environment.Lift(Main)
type error += type error +=
| Failed_to_checkout_context | Failed_to_checkout_context
@ -31,7 +29,7 @@ let () =
type incremental = { type incremental = {
predecessor: Client_baking_blocks.block_info ; predecessor: Client_baking_blocks.block_info ;
context : Context.t ; context : Context.t ;
state: Main.validation_state ; state: LiftedMain.validation_state ;
rev_operations: Operation.packed list ; rev_operations: Operation.packed list ;
header: Tezos_base.Block_header.shell_header ; header: Tezos_base.Block_header.shell_header ;
} }
@ -39,16 +37,14 @@ type incremental = {
let load_context ~context_path = let load_context ~context_path =
Context.init ~readonly:true context_path Context.init ~readonly:true context_path
let begin_construction (_cctxt : #Proto_alpha.full) index predecessor = let begin_construction ~timestamp ?protocol_data index predecessor =
let { Client_baking_blocks.context } = predecessor in let { Client_baking_blocks.context } = predecessor in
Context.checkout index context >>= function Context.checkout index context >>= function
| None -> fail Failed_to_checkout_context | None -> fail Failed_to_checkout_context
| Some context -> | Some context ->
let timestamp = Time.now () in
let predecessor_hash = predecessor.hash in
let header : Tezos_base.Block_header.shell_header = Tezos_base.Block_header.{ let header : Tezos_base.Block_header.shell_header = Tezos_base.Block_header.{
predecessor = predecessor_hash ; predecessor = predecessor.hash ;
proto_level = 0 ; proto_level = predecessor.proto_level ;
validation_passes = 0 ; validation_passes = 0 ;
fitness = predecessor.fitness ; fitness = predecessor.fitness ;
timestamp ; timestamp ;
@ -56,13 +52,14 @@ let begin_construction (_cctxt : #Proto_alpha.full) index predecessor =
context = Context_hash.zero ; context = Context_hash.zero ;
operations_hash = Operation_list_list_hash.zero ; operations_hash = Operation_list_list_hash.zero ;
} in } in
Main.begin_construction LiftedMain.begin_construction
~chain_id: predecessor.chain_id ~chain_id:predecessor.chain_id
~predecessor_context: context ~predecessor_context: context
~predecessor_timestamp: header.timestamp ~predecessor_timestamp: predecessor.timestamp
~predecessor_fitness: header.fitness ~predecessor_fitness: predecessor.fitness
~predecessor_level: header.level ~predecessor_level: (Raw_level.to_int32 predecessor.level)
~predecessor:predecessor_hash ~predecessor: predecessor.hash
?protocol_data
~timestamp ~timestamp
() >>=? fun state -> () >>=? fun state ->
return { return {
@ -74,8 +71,8 @@ let begin_construction (_cctxt : #Proto_alpha.full) index predecessor =
} }
let add_operation st ( op : Operation.packed ) = let add_operation st ( op : Operation.packed ) =
Main.apply_operation st.state op >>=? fun (state, _) -> LiftedMain.apply_operation st.state op >>=? fun (state, _) ->
return { st with state ; rev_operations = op :: st.rev_operations } return { st with state ; rev_operations = op :: st.rev_operations }
let finalize_construction inc = let finalize_construction inc =
Main.finalize_block inc.state >>=? fun _ -> return_unit LiftedMain.finalize_block inc.state

View File

@ -20,8 +20,8 @@ type incremental = {
val load_context : context_path:string -> Context.index Lwt.t val load_context : context_path:string -> Context.index Lwt.t
val begin_construction : #Proto_alpha.full -> Context.index -> Client_baking_blocks.block_info -> incremental tzresult Lwt.t val begin_construction : timestamp:Time.t -> ?protocol_data: block_header_data -> Context.index -> Client_baking_blocks.block_info -> incremental tzresult Lwt.t
val add_operation : incremental -> Operation.packed -> incremental tzresult Lwt.t val add_operation : incremental -> Operation.packed -> incremental tzresult Lwt.t
val finalize_construction : incremental -> unit tzresult Lwt.t val finalize_construction : incremental -> (T.validation_result * LiftedMain.block_header_metadata) tzresult Lwt.t