Shell/RPC: use query parameters in /injection

This commit is contained in:
Grégoire Henry 2018-04-21 14:21:46 +02:00 committed by Benjamin Canou
parent 9bc7b1e573
commit 2a93a336aa
20 changed files with 336 additions and 284 deletions

View File

@ -39,7 +39,7 @@ let commands () =
Lwt.catch
(fun () ->
Lwt_utils_unix.Protocol.read_dir dirname >>=? fun (_hash, proto) ->
Shell_services.inject_protocol cctxt proto >>= function
Injection_services.protocol cctxt proto >>= function
| Ok hash ->
cctxt#message "Injected protocol %a successfully" Protocol_hash.pp_short hash >>= fun () ->
return ()

View File

@ -40,12 +40,13 @@ let commands () = Clic.[
(prefixes [ "bootstrapped" ] @@
stop)
(fun () (cctxt : #Client_context.full) ->
Shell_services.bootstrapped cctxt >>=? fun (stream, _) ->
Monitor_services.bootstrapped cctxt >>=? fun (stream, _) ->
Lwt_stream.iter_s
(fun (hash, time) ->
cctxt#message "Current head: %a (%a)"
cctxt#message "Current head: %a (timestamp: %a, validation: %a)"
Block_hash.pp_short hash
Time.pp_hum time) stream >>= fun () ->
Time.pp_hum time
Time.pp_hum (Time.now ())) stream >>= fun () ->
cctxt#answer "Bootstrapped." >>= fun () ->
return ()
)

View File

@ -0,0 +1,84 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2018. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
let read_chain_id validator chain =
let distributed_db = Validator.distributed_db validator in
let state = Distributed_db.state distributed_db in
begin
match chain with
| None -> Lwt.return_none
| Some chain ->
Chain_directory.get_chain_id state chain >>= Lwt.return_some
end
let inject_block validator ?force ?chain bytes operations =
read_chain_id validator chain >>= fun chain_id ->
Validator.validate_block
validator ?force ?chain_id bytes operations >>=? fun (hash, block) ->
return (hash, (block >>=? fun _ -> return ()))
let inject_operation validator ?chain bytes =
read_chain_id validator chain >>= fun chain_id ->
let t =
match Data_encoding.Binary.of_bytes Operation.encoding bytes with
| None -> failwith "Can't parse the operation"
| Some op ->
Validator.inject_operation validator ?chain_id op in
let hash = Operation_hash.hash_bytes [bytes] in
Lwt.return (hash, t)
let inject_protocol state ?force:_ proto =
let proto_bytes =
Data_encoding.Binary.to_bytes_exn Protocol.encoding proto in
let hash = Protocol_hash.hash_bytes [proto_bytes] in
let validation =
Updater.compile hash proto >>= function
| false ->
failwith
"Compilation failed (%a)"
Protocol_hash.pp_short hash
| true ->
State.Protocol.store state proto >>= function
| None ->
failwith
"Previously registered protocol (%a)"
Protocol_hash.pp_short hash
| Some _ -> return ()
in
Lwt.return (hash, validation)
let build_rpc_directory validator =
let distributed_db = Validator.distributed_db validator in
let state = Distributed_db.state distributed_db in
let dir : unit RPC_directory.t ref = ref RPC_directory.empty in
let register0 s f =
dir := RPC_directory.register !dir s (fun () p q -> f p q) in
register0 Injection_services.S.block begin fun q (raw, operations) ->
inject_block validator
?chain:q#chain ~force:q#force raw operations >>=? fun (hash, wait) ->
(if q#async then return () else wait) >>=? fun () ->
return hash
end ;
register0 Injection_services.S.operation begin fun q contents ->
inject_operation validator ?chain:q#chain contents >>= fun (hash, wait) ->
(if q#async then return () else wait) >>=? fun () ->
return hash
end ;
register0 Injection_services.S.protocol begin fun q protocol ->
inject_protocol state ~force:q#force protocol >>= fun (hash, wait) ->
(if q#async then return () else wait) >>=? fun () ->
return hash
end ;
!dir

View File

@ -0,0 +1,10 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2018. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
val build_rpc_directory: Validator.t -> unit RPC_directory.t

View File

@ -7,7 +7,7 @@
(* *)
(**************************************************************************)
let build_rpc_directory validator =
let build_rpc_directory validator mainchain_validator =
let distributed_db = Validator.distributed_db validator in
let state = Distributed_db.state distributed_db in
@ -18,6 +18,30 @@ let build_rpc_directory validator =
let gen_register1 s f =
dir := RPC_directory.gen_register !dir s (fun ((), a) p q -> f a p q) in
gen_register0 Monitor_services.S.bootstrapped begin fun () () ->
let block_stream, stopper =
Chain_validator.new_head_watcher mainchain_validator in
let first_run = ref true in
let next () =
if !first_run then begin
first_run := false ;
let chain_state = Chain_validator.chain_state mainchain_validator in
Chain.head chain_state >>= fun head ->
let head_hash = State.Block.hash head in
let head_header = State.Block.header head in
Lwt.return (Some (head_hash, head_header.shell.timestamp))
end else begin
Lwt.pick [
( Lwt_stream.get block_stream >|=
Option.map ~f:(fun b ->
(State.Block.hash b, (State.Block.header b).shell.timestamp)) ) ;
(Chain_validator.bootstrapped mainchain_validator >|= fun () -> None) ;
]
end in
let shutdown () = Lwt_watcher.shutdown stopper in
RPC_answer.return_stream { next ; shutdown }
end ;
gen_register0 Monitor_services.S.valid_blocks begin fun q () ->
let block_stream, stopper = State.watcher state in
let shutdown () = Lwt_watcher.shutdown stopper in

View File

@ -7,4 +7,5 @@
(* *)
(**************************************************************************)
val build_rpc_directory: Validator.t -> unit RPC_directory.t
val build_rpc_directory:
Validator.t -> Chain_validator.t -> unit RPC_directory.t

View File

@ -139,9 +139,10 @@ let build_rpc_directory node =
dir := RPC_directory.register !dir s (fun () p q -> f p q) in
merge (Protocol_directory.build_rpc_directory node.state) ;
merge (Monitor_directory.build_rpc_directory node.validator) ;
merge (Shell_directory.build_rpc_directory
merge (Monitor_directory.build_rpc_directory
node.validator node.mainchain_validator) ;
merge Shell_directory.rpc_directory ;
merge (Injection_directory.build_rpc_directory node.validator) ;
merge (Chain_directory.build_rpc_directory node.validator) ;
merge (P2p.build_rpc_directory node.p2p) ;
merge Worker_directory.rpc_directory ;

View File

@ -7,49 +7,9 @@
(* *)
(**************************************************************************)
let inject_block validator ?force ?chain_id bytes operations =
Validator.validate_block
validator ?force ?chain_id bytes operations >>=? fun (hash, block) ->
return (hash, (block >>=? fun _ -> return ()))
let inject_operation validator ?chain_id bytes =
let t =
match Data_encoding.Binary.of_bytes Operation.encoding bytes with
| None -> failwith "Can't parse the operation"
| Some op ->
Validator.inject_operation validator ?chain_id op
in
let hash = Operation_hash.hash_bytes [bytes] in
Lwt.return (hash, t)
let inject_protocol state ?force:_ proto =
let proto_bytes =
Data_encoding.Binary.to_bytes_exn Protocol.encoding proto in
let hash = Protocol_hash.hash_bytes [proto_bytes] in
let validation =
Updater.compile hash proto >>= function
| false ->
failwith
"Compilation failed (%a)"
Protocol_hash.pp_short hash
| true ->
State.Protocol.store state proto >>= function
| None ->
failwith
"Previously registered protocol (%a)"
Protocol_hash.pp_short hash
| Some _ -> return ()
in
Lwt.return (hash, validation)
let build_rpc_directory validator mainchain_validator =
let distributed_db = Validator.distributed_db validator in
let state = Distributed_db.state distributed_db in
let rpc_directory =
let dir : unit RPC_directory.t ref = ref RPC_directory.empty in
let gen_register0 s f =
dir := RPC_directory.gen_register !dir s (fun () p q -> f p q) in
let register0 s f =
dir := RPC_directory.register !dir s (fun () p q -> f p q) in
@ -57,49 +17,4 @@ let build_rpc_directory validator mainchain_validator =
return (Data_encoding.Binary.to_bytes_exn Block_header.encoding header)
end ;
register0 Shell_services.S.inject_block begin fun () p ->
let { Shell_services.S.raw ; blocking ; force ; operations } = p in
inject_block validator ~force raw operations >>=? fun (hash, wait) ->
(if blocking then wait else return ()) >>=? fun () ->
return hash
end ;
register0 Shell_services.S.inject_operation begin fun () p ->
let (contents, blocking, chain_id) = p in
inject_operation validator ?chain_id contents >>= fun (hash, wait) ->
(if blocking then wait else return ()) >>=? fun () ->
return hash
end ;
register0 Shell_services.S.inject_protocol begin fun () p ->
let (proto, blocking, force) = p in
inject_protocol state ?force proto >>= fun (hash, wait) ->
(if blocking then wait else return ()) >>=? fun () ->
return hash
end ;
gen_register0 Shell_services.S.bootstrapped begin fun () () ->
let block_stream, stopper =
Chain_validator.new_head_watcher mainchain_validator in
let first_run = ref true in
let next () =
if !first_run then begin
first_run := false ;
let chain_state = Chain_validator.chain_state mainchain_validator in
Chain.head chain_state >>= fun head ->
let head_hash = State.Block.hash head in
let head_header = State.Block.header head in
Lwt.return (Some (head_hash, head_header.shell.timestamp))
end else begin
Lwt.pick [
( Lwt_stream.get block_stream >|=
Option.map ~f:(fun b ->
(State.Block.hash b, (State.Block.header b).shell.timestamp)) ) ;
(Chain_validator.bootstrapped mainchain_validator >|= fun () -> None) ;
]
end in
let shutdown () = Lwt_watcher.shutdown stopper in
RPC_answer.return_stream { next ; shutdown }
end ;
!dir

View File

@ -7,5 +7,4 @@
(* *)
(**************************************************************************)
val build_rpc_directory:
Validator.t -> Chain_validator.t -> unit RPC_directory.t
val rpc_directory: unit RPC_directory.t

View File

@ -0,0 +1,122 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2018. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
module S = struct
open Data_encoding
let path = RPC_path.(root / "injection")
let block_query =
let open RPC_query in
query (fun async force chain -> object
method async = async
method force = force
method chain = chain
end)
|+ flag "async" (fun t -> t#async)
|+ flag "force" (fun t -> t#force)
|+ opt_field "chain" Chain_services.chain_arg (fun t -> t#chain)
|> seal
let block_param =
obj2
(req "data" bytes)
(req "operations"
(list (dynamic_size (list (dynamic_size Operation.encoding)))))
let block =
RPC_service.post_service
~description:
"Inject a block in the node and broadcast it. The `operations` \
embedded in `blockHeader` might be pre-validated using a \
contextual RPCs from the latest block \
(e.g. '/blocks/head/context/preapply'). Returns the ID of the \
block. By default, the RPC will wait for the block to be \
validated before answering."
~query: block_query
~input: block_param
~output: Block_hash.encoding
RPC_path.(path / "block")
let operation_query =
let open RPC_query in
query (fun async chain -> object
method async = async
method chain = chain
end)
|+ flag "async" (fun t -> t#async)
|+ opt_field "chain" Chain_services.chain_arg (fun t -> t#chain)
|> seal
let operation =
RPC_service.post_service
~description:
"Inject an operation in node and broadcast it. Returns the \
ID of the operation. The `signedOperationContents` should be \
constructed using a contextual RPCs from the latest block \
and signed by the client. By default, the RPC will wait for \
the operation to be (pre-)validated before answering. See \
RPCs under /blocks/prevalidation for more details on the \
prevalidation context."
~query: operation_query
~input: bytes
~output: Operation_hash.encoding
RPC_path.(path / "operation")
let protocol_query =
let open RPC_query in
query (fun async force -> object
method async = async
method force = force
end)
|+ flag "async" (fun t -> t#async)
|+ flag "force" (fun t -> t#force)
|> seal
let protocol =
RPC_service.post_service
~description:
"Inject a protocol in node. Returns the ID of the protocol."
~query: protocol_query
~input: Protocol.encoding
~output: Protocol_hash.encoding
RPC_path.(path / "protocol")
end
open RPC_context
let block ctxt
?(async = false) ?(force = false) ?chain
raw operations =
make_call S.block ctxt ()
(object
method async = async
method force = force
method chain = chain
end)
(raw, operations)
let operation ctxt ?(async = false) ?chain operation =
make_call S.operation ctxt ()
(object
method async = async
method chain = chain
end)
operation
let protocol ctxt ?(async = false) ?(force = false) protocol =
make_call S.protocol ctxt ()
(object
method async = async
method force = force
end)
protocol

View File

@ -0,0 +1,56 @@
(**************************************************************************)
(* *)
(* Copyright (c) 2014 - 2018. *)
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
open RPC_context
val block:
#simple ->
?async:bool -> ?force:bool -> ?chain:Chain_services.chain ->
MBytes.t -> Operation.t list list ->
Block_hash.t tzresult Lwt.t
(** [block cctxt ?async ?force raw_block] tries to inject
[raw_block] inside the node. If [?async] is [true], [raw_block]
will be validated before the result is returned. If [?force] is
true, the block will be injected even on non strictly increasing
fitness. *)
val operation:
#simple ->
?async:bool -> ?chain:Chain_services.chain ->
MBytes.t ->
Operation_hash.t tzresult Lwt.t
val protocol:
#simple ->
?async:bool -> ?force:bool ->
Protocol.t ->
Protocol_hash.t tzresult Lwt.t
module S : sig
val block:
([ `POST ], unit,
unit, < async: bool ;
force: bool ;
chain: Chain_services.chain option >, MBytes.t * Operation.t list list,
Block_hash.t) RPC_service.t
val operation:
([ `POST ], unit,
unit, < async : bool;
chain : Chain_services.chain option >, MBytes.t,
Operation_hash.t) RPC_service.t
val protocol:
([ `POST ], unit,
unit, < async : bool;
force : bool >, Protocol.t,
Protocol_hash.t) RPC_service.t
end

View File

@ -10,8 +10,18 @@
module S = struct
open Data_encoding
let path = RPC_path.(root / "monitor")
let bootstrapped =
RPC_service.get_service
~description:""
~query: RPC_query.empty
~output: (obj2
(req "block" Block_hash.encoding)
(req "timestamp" Time.encoding))
RPC_path.(path / "bootstrapped")
let valid_blocks_query =
let open RPC_query in
query (fun protocols next_protocols chains -> object
@ -63,6 +73,9 @@ end
open RPC_context
let bootstrapped ctxt =
make_streamed_call S.bootstrapped ctxt () () ()
let valid_blocks
ctxt ?(chains = [`Main]) ?(protocols = []) ?(next_protocols = []) () =
make_streamed_call S.valid_blocks ctxt () (object

View File

@ -9,6 +9,9 @@
open RPC_context
val bootstrapped:
#streamed -> ((Block_hash.t * Time.t) Lwt_stream.t * stopper) tzresult Lwt.t
val valid_blocks:
#streamed ->
?chains:Chain_services.chain list ->
@ -28,6 +31,11 @@ val protocols:
module S : sig
val bootstrapped:
([ `GET ], unit,
unit, unit, unit,
Block_hash.t * Time.t) RPC_service.t
val valid_blocks:
([ `GET ], unit,
unit, < chains : Chain_services.chain list;

View File

@ -19,131 +19,9 @@ module S = struct
~output: (obj1 (req "block" bytes))
RPC_path.(root / "forge_block_header")
type inject_block_param = {
raw: MBytes.t ;
blocking: bool ;
force: bool ;
chain_id: Chain_id.t option ;
operations: Operation.t list list ;
}
let inject_block_param =
conv
(fun { raw ; blocking ; force ; chain_id ; operations } ->
(raw, blocking, force, chain_id, operations))
(fun (raw, blocking, force, chain_id, operations) ->
{ raw ; blocking ; force ; chain_id ; operations })
(obj5
(req "data" bytes)
(dft "blocking"
~description:
"Should the RPC wait for the block to be \
validated before answering. (default: true)"
bool
true)
(dft "force"
~description:
"Should we inject the block when its fitness is below \
the current head. (default: false)"
bool
false)
(opt "chain_id" Chain_id.encoding)
(req "operations"
(list (list (dynamic_size Operation.encoding)))))
let inject_block =
RPC_service.post_service
~description:
"Inject a block in the node and broadcast it. The `operations` \
embedded in `blockHeader` might be pre-validated using a \
contextual RPCs from the latest block \
(e.g. '/blocks/head/context/preapply'). Returns the ID of the \
block. By default, the RPC will wait for the block to be \
validated before answering."
~query: RPC_query.empty
~input: inject_block_param
~output: (obj1 (req "block_hash" Block_hash.encoding))
RPC_path.(root / "inject_block")
let inject_operation =
RPC_service.post_service
~description:
"Inject an operation in node and broadcast it. Returns the \
ID of the operation. The `signedOperationContents` should be \
constructed using a contextual RPCs from the latest block \
and signed by the client. By default, the RPC will wait for \
the operation to be (pre-)validated before answering. See \
RPCs under /blocks/prevalidation for more details on the \
prevalidation context."
~query: RPC_query.empty
~input:
(obj3
(req "signedOperationContents"
~title: "Tezos signed operation (hex encoded)"
bytes)
(dft "blocking"
~description:
"Should the RPC wait for the operation to be \
(pre-)validated before answering. (default: true)"
bool
true)
(opt "chain_id" Chain_id.encoding))
~output:
(obj1 (req "injectedOperation" Operation_hash.encoding))
RPC_path.(root / "inject_operation")
let inject_protocol =
RPC_service.post_service
~description:
"Inject a protocol in node. Returns the ID of the protocol."
~query: RPC_query.empty
~input:
(obj3
(req "protocol" Protocol.encoding)
(dft "blocking"
~description:
"Should the RPC wait for the protocol to be \
validated before answering. (default: true)"
bool
true)
(opt "force"
~description:
"Should we inject protocol that is invalid. (default: false)"
bool))
~output:
(obj1 (req "injectedProtocol" Protocol_hash.encoding))
RPC_path.(root / "inject_protocol")
let bootstrapped =
RPC_service.post_service
~description:""
~query: RPC_query.empty
~input: empty
~output: (obj2
(req "block" Block_hash.encoding)
(req "timestamp" Time.encoding))
RPC_path.(root / "bootstrapped")
end
open RPC_context
let forge_block_header ctxt header =
make_call S.forge_block_header ctxt () () header
let inject_block ctxt
?(async = false) ?(force = false) ?chain_id
raw operations =
make_call S.inject_block ctxt () ()
{ raw ; blocking = not async ; force ; chain_id ; operations }
let inject_operation ctxt ?(async = false) ?chain_id operation =
make_call S.inject_operation ctxt () ()
(operation, not async, chain_id)
let inject_protocol ctxt ?(async = false) ?force protocol =
make_call S.inject_protocol ctxt () ()
(protocol, not async, force)
let bootstrapped ctxt =
make_streamed_call S.bootstrapped ctxt () () ()

View File

@ -14,32 +14,6 @@ val forge_block_header:
Block_header.t ->
MBytes.t tzresult Lwt.t
val inject_block:
#simple ->
?async:bool -> ?force:bool -> ?chain_id:Chain_id.t ->
MBytes.t -> Operation.t list list ->
Block_hash.t tzresult Lwt.t
(** [inject_block cctxt ?async ?force raw_block] tries to inject
[raw_block] inside the node. If [?async] is [true], [raw_block]
will be validated before the result is returned. If [?force] is
true, the block will be injected even on non strictly increasing
fitness. *)
val inject_operation:
#simple ->
?async:bool -> ?chain_id:Chain_id.t ->
MBytes.t ->
Operation_hash.t tzresult Lwt.t
val inject_protocol:
#simple ->
?async:bool -> ?force:bool ->
Protocol.t ->
Protocol_hash.t tzresult Lwt.t
val bootstrapped:
#streamed -> ((Block_hash.t * Time.t) Lwt_stream.t * stopper) tzresult Lwt.t
module S : sig
val forge_block_header:
@ -47,32 +21,4 @@ module S : sig
unit, unit, Block_header.t,
MBytes.t) RPC_service.t
type inject_block_param = {
raw: MBytes.t ;
blocking: bool ;
force: bool ;
chain_id: Chain_id.t option ;
operations: Operation.t list list ;
}
val inject_block:
([ `POST ], unit,
unit, unit, inject_block_param,
Block_hash.t) RPC_service.t
val inject_operation:
([ `POST ], unit,
unit, unit, (MBytes.t * bool * Chain_id.t option),
Operation_hash.t) RPC_service.t
val inject_protocol:
([ `POST ], unit,
unit, unit, (Protocol.t * bool * bool option),
Protocol_hash.t) RPC_service.t
val bootstrapped:
([ `POST ], unit,
unit, unit, unit,
Block_hash.t * Time.t) RPC_service.t
end

View File

@ -95,7 +95,6 @@ let inject_endorsement
(cctxt : #Proto_alpha.full)
?(chain = `Main) block level ?async
src_sk slots =
Chain_services.chain_id cctxt ~chain () >>=? fun chain_id ->
Block_services.hash cctxt ~chain ~block () >>=? fun hash ->
Alpha_services.Forge.Consensus.endorsement cctxt
(chain, block)
@ -106,8 +105,7 @@ let inject_endorsement
() >>=? fun bytes ->
Client_keys.append
src_sk ~watermark:Endorsement bytes >>=? fun signed_bytes ->
Shell_services.inject_operation
cctxt ?async ~chain_id signed_bytes >>=? fun oph ->
Injection_services.operation cctxt ?async ~chain signed_bytes >>=? fun oph ->
iter_s
(fun slot ->
State.record_endorsement cctxt level hash slot oph)

View File

@ -71,9 +71,8 @@ let inject_block cctxt
let block = `Hash (shell_header.Tezos_base.Block_header.predecessor, 0) in
forge_block_header cctxt ~chain block
src_sk shell_header priority seed_nonce_hash >>=? fun signed_header ->
Chain_services.chain_id cctxt ~chain () >>=? fun chain_id ->
Shell_services.inject_block cctxt
?force ~chain_id signed_header operations >>=? fun block_hash ->
Injection_services.block cctxt
?force ~chain signed_header operations >>=? fun block_hash ->
return block_hash
type error +=

View File

@ -15,12 +15,10 @@ let inject_seed_nonce_revelation rpc_config ?(chain = `Main) block ?async nonces
List.map
(fun (level, nonce) ->
Seed_nonce_revelation { level ; nonce }) nonces in
Chain_services.chain_id rpc_config ~chain () >>=? fun chain_id ->
Block_services.hash rpc_config ~chain ~block () >>=? fun branch ->
Alpha_services.Forge.Anonymous.operations rpc_config
(chain, block) ~branch operations >>=? fun bytes ->
Shell_services.inject_operation
rpc_config ?async ~chain_id bytes >>=? fun oph ->
Injection_services.operation rpc_config ?async ~chain bytes >>=? fun oph ->
return oph
let forge_seed_nonce_revelation

View File

@ -176,8 +176,7 @@ let inject_operation
Lwt.return res
end >>=? fun () ->
let bytes = Data_encoding.Binary.to_bytes_exn Operation.encoding op in
Chain_services.chain_id cctxt ~chain () >>=? fun chain_id ->
Shell_services.inject_operation cctxt ~chain_id bytes >>=? fun oph ->
Injection_services.operation cctxt ~chain bytes >>=? fun oph ->
cctxt#message "Operation successfully injected in the node." >>= fun () ->
cctxt#message "Operation hash is '%a'." Operation_hash.pp oph >>= fun () ->
begin

View File

@ -20,7 +20,7 @@ let bake cctxt ?(timestamp = Time.now ()) block command sk =
[] >>=? fun (shell_header, _) ->
let blk = Data.Command.forge shell_header command in
Client_keys.append sk blk >>=? fun signed_blk ->
Shell_services.inject_block cctxt signed_blk []
Injection_services.block cctxt signed_blk []
let int64_parameter =
(Clic.parameter (fun _ p ->