Shell/Proto: more complete "begin_construction".
The `begin_construction` function now accepts an optional argument `proto_header`. This is to be used by a new RPC that ease forging the shell header of a block (i.e. it will compute the fitness and, in a near future, the hash of the resulting context).
This commit is contained in:
parent
89814a3d4e
commit
3b7a314669
@ -155,6 +155,7 @@ let start_prevalidation
|
|||||||
~predecessor_level
|
~predecessor_level
|
||||||
~predecessor
|
~predecessor
|
||||||
~timestamp
|
~timestamp
|
||||||
|
()
|
||||||
>>=? fun state ->
|
>>=? fun state ->
|
||||||
return (State { proto = (module Proto) ; state })
|
return (State { proto = (module Proto) ; state })
|
||||||
|
|
||||||
|
@ -62,7 +62,8 @@ module type PROTOCOL = sig
|
|||||||
predecessor_fitness: Fitness.t ->
|
predecessor_fitness: Fitness.t ->
|
||||||
predecessor: Block_hash.t ->
|
predecessor: Block_hash.t ->
|
||||||
timestamp: Time.t ->
|
timestamp: Time.t ->
|
||||||
validation_state tzresult Lwt.t
|
?proto_header: MBytes.t ->
|
||||||
|
unit -> validation_state tzresult Lwt.t
|
||||||
val apply_operation :
|
val apply_operation :
|
||||||
validation_state -> operation -> validation_state tzresult Lwt.t
|
validation_state -> operation -> validation_state tzresult Lwt.t
|
||||||
val finalize_block :
|
val finalize_block :
|
||||||
|
@ -50,11 +50,11 @@ let register (module Proto : Protocol_sigs.PACKED_PROTOCOL) =
|
|||||||
let begin_construction
|
let begin_construction
|
||||||
~predecessor_context ~predecessor_timestamp
|
~predecessor_context ~predecessor_timestamp
|
||||||
~predecessor_level ~predecessor_fitness
|
~predecessor_level ~predecessor_fitness
|
||||||
~predecessor ~timestamp =
|
~predecessor ~timestamp ?proto_header () =
|
||||||
begin_construction
|
begin_construction
|
||||||
~predecessor_context ~predecessor_timestamp
|
~predecessor_context ~predecessor_timestamp
|
||||||
~predecessor_level ~predecessor_fitness
|
~predecessor_level ~predecessor_fitness
|
||||||
~predecessor ~timestamp >|= wrap_error
|
~predecessor ~timestamp ?proto_header () >|= wrap_error
|
||||||
let current_context c =
|
let current_context c =
|
||||||
current_context c >|= wrap_error
|
current_context c >|= wrap_error
|
||||||
let apply_operation c o =
|
let apply_operation c o =
|
||||||
|
@ -247,24 +247,36 @@ let may_start_new_cycle ctxt =
|
|||||||
ctxt last_cycle reward_date >>=? fun ctxt ->
|
ctxt last_cycle reward_date >>=? fun ctxt ->
|
||||||
return ctxt
|
return ctxt
|
||||||
|
|
||||||
let begin_construction ctxt =
|
let begin_full_construction ctxt pred_timestamp proto_header =
|
||||||
Fitness.increase ctxt
|
Lwt.return
|
||||||
|
(Block_header.parse_unsigned_proto_header
|
||||||
|
proto_header) >>=? fun proto_header ->
|
||||||
|
Mining.check_mining_rights
|
||||||
|
ctxt proto_header pred_timestamp >>=? fun miner ->
|
||||||
|
Mining.pay_mining_bond ctxt proto_header miner >>=? fun ctxt ->
|
||||||
|
let ctxt = Fitness.increase ctxt in
|
||||||
|
return (ctxt, proto_header, miner)
|
||||||
|
|
||||||
let begin_application ctxt block pred_timestamp =
|
let begin_partial_construction ctxt =
|
||||||
Mining.check_proof_of_work_stamp ctxt block >>=? fun () ->
|
let ctxt = Fitness.increase ctxt in
|
||||||
Mining.check_fitness_gap ctxt block >>=? fun () ->
|
return ctxt
|
||||||
Mining.check_mining_rights ctxt block pred_timestamp >>=? fun miner ->
|
|
||||||
Mining.check_signature ctxt block miner >>=? fun () ->
|
let begin_application ctxt block_header pred_timestamp =
|
||||||
Mining.pay_mining_bond ctxt block miner >>=? fun ctxt ->
|
Mining.check_proof_of_work_stamp ctxt block_header >>=? fun () ->
|
||||||
|
Mining.check_fitness_gap ctxt block_header >>=? fun () ->
|
||||||
|
Mining.check_mining_rights
|
||||||
|
ctxt block_header.proto pred_timestamp >>=? fun miner ->
|
||||||
|
Mining.check_signature ctxt block_header miner >>=? fun () ->
|
||||||
|
Mining.pay_mining_bond ctxt block_header.proto miner >>=? fun ctxt ->
|
||||||
let ctxt = Fitness.increase ctxt in
|
let ctxt = Fitness.increase ctxt in
|
||||||
return (ctxt, miner)
|
return (ctxt, miner)
|
||||||
|
|
||||||
let finalize_application ctxt block miner =
|
let finalize_application ctxt block_proto_header miner =
|
||||||
(* end of level (from this point nothing should fail) *)
|
(* end of level (from this point nothing should fail) *)
|
||||||
let priority = block.Block_header.proto.priority in
|
let priority = block_proto_header.Block_header.priority in
|
||||||
let reward = Mining.base_mining_reward ctxt ~priority in
|
let reward = Mining.base_mining_reward ctxt ~priority in
|
||||||
Nonce.record_hash ctxt
|
Nonce.record_hash ctxt
|
||||||
miner reward block.proto.seed_nonce_hash >>=? fun ctxt ->
|
miner reward block_proto_header.seed_nonce_hash >>=? fun ctxt ->
|
||||||
Reward.pay_due_rewards ctxt >>=? fun ctxt ->
|
Reward.pay_due_rewards ctxt >>=? fun ctxt ->
|
||||||
(* end of cycle *)
|
(* end of cycle *)
|
||||||
may_start_new_cycle ctxt >>=? fun ctxt ->
|
may_start_new_cycle ctxt >>=? fun ctxt ->
|
||||||
|
@ -92,9 +92,17 @@ let parse
|
|||||||
timestamp ; fitness ; operations_hash } in
|
timestamp ; fitness ; operations_hash } in
|
||||||
Ok { shell ; proto ; signature }
|
Ok { shell ; proto ; signature }
|
||||||
|
|
||||||
|
let parse_unsigned_proto_header bytes =
|
||||||
|
match Data_encoding.Binary.of_bytes proto_header_encoding bytes with
|
||||||
|
| None -> Error [Cant_parse_proto_header]
|
||||||
|
| Some proto -> Ok proto
|
||||||
|
|
||||||
let forge_unsigned shell proto =
|
let forge_unsigned shell proto =
|
||||||
Data_encoding.Binary.to_bytes unsigned_header_encoding (shell, proto)
|
Data_encoding.Binary.to_bytes unsigned_header_encoding (shell, proto)
|
||||||
|
|
||||||
|
let forge_unsigned_proto_header proto =
|
||||||
|
Data_encoding.Binary.to_bytes proto_header_encoding proto
|
||||||
|
|
||||||
let hash_raw = Block_header.hash
|
let hash_raw = Block_header.hash
|
||||||
let hash { shell ; proto ; signature } =
|
let hash { shell ; proto ; signature } =
|
||||||
Block_header.hash
|
Block_header.hash
|
||||||
|
@ -36,7 +36,15 @@ val max_header_length: int
|
|||||||
(** The maximum size of block headers in bytes *)
|
(** The maximum size of block headers in bytes *)
|
||||||
|
|
||||||
val parse: Block_header.t -> block_header tzresult
|
val parse: Block_header.t -> block_header tzresult
|
||||||
(** Parse the protocol-specific part of a block header. *)
|
(** Parse the (signed) protocol-specific part of a block header. *)
|
||||||
|
|
||||||
|
val parse_unsigned_proto_header: MBytes.t -> proto_header tzresult
|
||||||
|
(** Parse the (unsigned) protocol-specific part of a block header. *)
|
||||||
|
|
||||||
|
val forge_unsigned_proto_header: proto_header -> MBytes.t
|
||||||
|
(** [forge_header proto_hdr] is the binary serialization
|
||||||
|
(using [proto_header_encoding]) of the protocol-specific part
|
||||||
|
of a block header, without the signature. *)
|
||||||
|
|
||||||
val forge_unsigned:
|
val forge_unsigned:
|
||||||
Block_header.shell_header -> proto_header -> MBytes.t
|
Block_header.shell_header -> proto_header -> MBytes.t
|
||||||
|
@ -25,8 +25,18 @@ let max_block_length =
|
|||||||
let rpc_services = Services_registration.rpc_services
|
let rpc_services = Services_registration.rpc_services
|
||||||
|
|
||||||
type validation_mode =
|
type validation_mode =
|
||||||
| Application of Tezos_context.Block_header.t * Tezos_context.public_key_hash
|
| Application of {
|
||||||
| Construction of { pred_block : Block_hash.t ; timestamp : Time.t }
|
block_header : Tezos_context.Block_header.t ;
|
||||||
|
miner : Tezos_context.public_key_hash ;
|
||||||
|
}
|
||||||
|
| Partial_construction of {
|
||||||
|
predecessor : Block_hash.t ;
|
||||||
|
}
|
||||||
|
| Full_construction of {
|
||||||
|
predecessor : Block_hash.t ;
|
||||||
|
block_proto_header : Tezos_context.Block_header.proto_header ;
|
||||||
|
miner : Tezos_context.public_key_hash ;
|
||||||
|
}
|
||||||
|
|
||||||
type validation_state =
|
type validation_state =
|
||||||
{ mode : validation_mode ;
|
{ mode : validation_mode ;
|
||||||
@ -49,38 +59,56 @@ let begin_application
|
|||||||
~predecessor_timestamp:pred_timestamp
|
~predecessor_timestamp:pred_timestamp
|
||||||
~predecessor_fitness:pred_fitness
|
~predecessor_fitness:pred_fitness
|
||||||
raw_block =
|
raw_block =
|
||||||
Lwt.return (Tezos_context.Block_header.parse raw_block) >>=? fun header ->
|
Lwt.return (Tezos_context.Block_header.parse raw_block) >>=? fun block_header ->
|
||||||
let level = header.shell.level in
|
let level = block_header.shell.level in
|
||||||
let fitness = pred_fitness in
|
let fitness = pred_fitness in
|
||||||
let timestamp = header.shell.timestamp in
|
let timestamp = block_header.shell.timestamp in
|
||||||
Tezos_context.init ~level ~timestamp ~fitness ctxt >>=? fun ctxt ->
|
Tezos_context.init ~level ~timestamp ~fitness ctxt >>=? fun ctxt ->
|
||||||
Apply.begin_application ctxt header pred_timestamp >>=? fun (ctxt, miner) ->
|
Apply.begin_application
|
||||||
let mode = Application (header, miner) in
|
ctxt block_header pred_timestamp >>=? fun (ctxt, miner) ->
|
||||||
|
let mode = Application { block_header ; miner } in
|
||||||
return { mode ; ctxt ; op_count = 0 }
|
return { mode ; ctxt ; op_count = 0 }
|
||||||
|
|
||||||
let begin_construction
|
let begin_construction
|
||||||
~predecessor_context:ctxt
|
~predecessor_context:ctxt
|
||||||
~predecessor_timestamp:_
|
~predecessor_timestamp:pred_timestamp
|
||||||
~predecessor_level:pred_level
|
~predecessor_level:pred_level
|
||||||
~predecessor_fitness:pred_fitness
|
~predecessor_fitness:pred_fitness
|
||||||
~predecessor:pred_block
|
~predecessor
|
||||||
~timestamp =
|
~timestamp
|
||||||
let mode = Construction { pred_block ; timestamp } in
|
?proto_header
|
||||||
let level = Int32.succ pred_level in
|
() =
|
||||||
|
let level = Int32.succ pred_level in
|
||||||
let fitness = pred_fitness in
|
let fitness = pred_fitness in
|
||||||
Tezos_context.init ~timestamp ~level ~fitness ctxt >>=? fun ctxt ->
|
Tezos_context.init ~timestamp ~level ~fitness ctxt >>=? fun ctxt ->
|
||||||
let ctxt = Apply.begin_construction ctxt in
|
begin
|
||||||
|
match proto_header with
|
||||||
|
| None ->
|
||||||
|
Apply.begin_partial_construction ctxt >>=? fun ctxt ->
|
||||||
|
let mode = Partial_construction { predecessor } in
|
||||||
|
return (mode, ctxt)
|
||||||
|
| Some proto_header ->
|
||||||
|
Apply.begin_full_construction
|
||||||
|
ctxt pred_timestamp
|
||||||
|
proto_header >>=? fun (ctxt, block_proto_header, miner) ->
|
||||||
|
let mode =
|
||||||
|
Full_construction { predecessor ; miner ; block_proto_header } in
|
||||||
|
return (mode, ctxt)
|
||||||
|
end >>=? fun (mode, ctxt) ->
|
||||||
return { mode ; ctxt ; op_count = 0 }
|
return { mode ; ctxt ; op_count = 0 }
|
||||||
|
|
||||||
let apply_operation ({ mode ; ctxt ; op_count } as data) operation =
|
let apply_operation ({ mode ; ctxt ; op_count } as data) operation =
|
||||||
let pred_block, block_prio, miner_contract =
|
let pred_block, block_prio, miner_contract =
|
||||||
match mode with
|
match mode with
|
||||||
| Construction { pred_block } ->
|
| Partial_construction { predecessor } ->
|
||||||
pred_block, 0, None
|
predecessor, 0, None
|
||||||
| Application (block, delegate) ->
|
| Application
|
||||||
block.shell.predecessor,
|
{ miner ; block_header = { shell = { predecessor } ;
|
||||||
block.proto.priority,
|
proto = block_proto_header } }
|
||||||
Some (Tezos_context.Contract.default_contract delegate) in
|
| Full_construction { predecessor ; block_proto_header ; miner } ->
|
||||||
|
predecessor,
|
||||||
|
block_proto_header.priority,
|
||||||
|
Some (Tezos_context.Contract.default_contract miner) in
|
||||||
Apply.apply_operation
|
Apply.apply_operation
|
||||||
ctxt miner_contract pred_block block_prio operation
|
ctxt miner_contract pred_block block_prio operation
|
||||||
>>=? fun (ctxt, _contracts, _ignored_script_error) ->
|
>>=? fun (ctxt, _contracts, _ignored_script_error) ->
|
||||||
@ -88,14 +116,16 @@ let apply_operation ({ mode ; ctxt ; op_count } as data) operation =
|
|||||||
return { data with ctxt ; op_count }
|
return { data with ctxt ; op_count }
|
||||||
|
|
||||||
let finalize_block { mode ; ctxt ; op_count } = match mode with
|
let finalize_block { mode ; ctxt ; op_count } = match mode with
|
||||||
| Construction _ ->
|
| Partial_construction _ ->
|
||||||
let ctxt = Tezos_context.finalize ctxt in
|
let ctxt = Tezos_context.finalize ctxt in
|
||||||
return ctxt
|
return ctxt
|
||||||
| Application (block, miner) ->
|
| Application
|
||||||
Apply.finalize_application ctxt block miner >>=? fun ctxt ->
|
{ miner ; block_header = { proto = block_proto_header } }
|
||||||
|
| Full_construction { block_proto_header ; miner } ->
|
||||||
|
Apply.finalize_application ctxt block_proto_header miner >>=? fun ctxt ->
|
||||||
let { level } : Tezos_context.Level.t =
|
let { level } : Tezos_context.Level.t =
|
||||||
Tezos_context. Level.current ctxt in
|
Tezos_context. Level.current ctxt in
|
||||||
let priority = block.proto.priority in
|
let priority = block_proto_header.priority in
|
||||||
let level = Tezos_context.Raw_level.to_int32 level in
|
let level = Tezos_context.Raw_level.to_int32 level in
|
||||||
let fitness = Tezos_context.Fitness.current ctxt in
|
let fitness = Tezos_context.Fitness.current ctxt in
|
||||||
let commit_message =
|
let commit_message =
|
||||||
|
@ -118,14 +118,14 @@ let check_timestamp c priority pred_timestamp =
|
|||||||
fail_unless Timestamp.(minimal_time <= timestamp)
|
fail_unless Timestamp.(minimal_time <= timestamp)
|
||||||
(Timestamp_too_early (minimal_time, timestamp))
|
(Timestamp_too_early (minimal_time, timestamp))
|
||||||
|
|
||||||
let check_mining_rights c { Block_header.proto = { priority } }
|
let check_mining_rights c { Block_header.priority }
|
||||||
pred_timestamp =
|
pred_timestamp =
|
||||||
let level = Level.current c in
|
let level = Level.current c in
|
||||||
Roll.mining_rights_owner c level ~priority >>=? fun delegate ->
|
Roll.mining_rights_owner c level ~priority >>=? fun delegate ->
|
||||||
check_timestamp c priority pred_timestamp >>=? fun () ->
|
check_timestamp c priority pred_timestamp >>=? fun () ->
|
||||||
return delegate
|
return delegate
|
||||||
|
|
||||||
let pay_mining_bond c { Block_header.proto = { priority } } id =
|
let pay_mining_bond c { Block_header.priority } id =
|
||||||
if Compare.Int.(priority >= Constants.first_free_mining_slot c)
|
if Compare.Int.(priority >= Constants.first_free_mining_slot c)
|
||||||
then return c
|
then return c
|
||||||
else
|
else
|
||||||
|
@ -29,7 +29,7 @@ val minimal_time: context -> int -> Time.t -> Time.t tzresult Lwt.t
|
|||||||
|
|
||||||
val pay_mining_bond:
|
val pay_mining_bond:
|
||||||
context ->
|
context ->
|
||||||
Block_header.t ->
|
Block_header.proto_header ->
|
||||||
public_key_hash ->
|
public_key_hash ->
|
||||||
context tzresult Lwt.t
|
context tzresult Lwt.t
|
||||||
|
|
||||||
@ -42,7 +42,8 @@ val pay_endorsement_bond:
|
|||||||
* the bond have been payed if the slot is below [Constants.first_free_mining_slot].
|
* the bond have been payed if the slot is below [Constants.first_free_mining_slot].
|
||||||
*)
|
*)
|
||||||
val check_mining_rights:
|
val check_mining_rights:
|
||||||
context -> Block_header.t -> Time.t -> public_key_hash tzresult Lwt.t
|
context -> Block_header.proto_header -> Time.t ->
|
||||||
|
public_key_hash tzresult Lwt.t
|
||||||
|
|
||||||
(** [check_signing_rights c slot contract] verifies that:
|
(** [check_signing_rights c slot contract] verifies that:
|
||||||
* the slot is valid;
|
* the slot is valid;
|
||||||
|
@ -604,6 +604,24 @@ module Helpers = struct
|
|||||||
describe ~title: "hex encoded operation" bytes)))
|
describe ~title: "hex encoded operation" bytes)))
|
||||||
RPC.Path.(custom_root / "helpers" / "forge" / "operations" )
|
RPC.Path.(custom_root / "helpers" / "forge" / "operations" )
|
||||||
|
|
||||||
|
let empty_proof_of_work_nonce =
|
||||||
|
MBytes.of_string
|
||||||
|
(String.make Constants_repr.proof_of_work_nonce_size '\000')
|
||||||
|
|
||||||
|
let block_proto_header custom_root =
|
||||||
|
RPC.service
|
||||||
|
~description: "Forge the protocol-specific part of a block header"
|
||||||
|
~input:
|
||||||
|
(obj3
|
||||||
|
(req "priority" uint16)
|
||||||
|
(req "nonce_hash" Nonce_hash.encoding)
|
||||||
|
(dft "proof_of_work_nonce"
|
||||||
|
(Fixed.bytes
|
||||||
|
Tezos_context.Constants.proof_of_work_nonce_size)
|
||||||
|
empty_proof_of_work_nonce))
|
||||||
|
~output: (wrap_tzerror bytes)
|
||||||
|
RPC.Path.(custom_root / "helpers" / "forge" / "block_proto_header")
|
||||||
|
|
||||||
let block custom_root =
|
let block custom_root =
|
||||||
RPC.service
|
RPC.service
|
||||||
~description: "Forge a block header"
|
~description: "Forge a block header"
|
||||||
|
@ -488,6 +488,14 @@ let forge_operations _ctxt (shell, proto) =
|
|||||||
|
|
||||||
let () = register1 Services.Helpers.Forge.operations forge_operations
|
let () = register1 Services.Helpers.Forge.operations forge_operations
|
||||||
|
|
||||||
|
let forge_block_proto_header _ctxt
|
||||||
|
(priority, seed_nonce_hash, proof_of_work_nonce) : MBytes.t tzresult Lwt.t =
|
||||||
|
return (Block_header.forge_unsigned_proto_header
|
||||||
|
{ priority ; seed_nonce_hash ; proof_of_work_nonce })
|
||||||
|
|
||||||
|
let () =
|
||||||
|
register1 Services.Helpers.Forge.block_proto_header forge_block_proto_header
|
||||||
|
|
||||||
let forge_block _ctxt
|
let forge_block _ctxt
|
||||||
((net_id, predecessor, timestamp, fitness, operations_hash),
|
((net_id, predecessor, timestamp, fitness, operations_hash),
|
||||||
(level, priority, proto_level, seed_nonce_hash, proof_of_work_nonce)) : MBytes.t tzresult Lwt.t =
|
(level, priority, proto_level, seed_nonce_hash, proof_of_work_nonce)) : MBytes.t tzresult Lwt.t =
|
||||||
|
@ -562,6 +562,14 @@ module Block_header : sig
|
|||||||
val parse: Block_header.t -> block_header tzresult
|
val parse: Block_header.t -> block_header tzresult
|
||||||
(** Parse the protocol-specific part of a block header. *)
|
(** Parse the protocol-specific part of a block header. *)
|
||||||
|
|
||||||
|
val parse_unsigned_proto_header: MBytes.t -> proto_header tzresult
|
||||||
|
(** Parse the (unsigned) protocol-specific part of a block header. *)
|
||||||
|
|
||||||
|
val forge_unsigned_proto_header: proto_header -> MBytes.t
|
||||||
|
(** [forge_header proto_hdr] is the binary serialization
|
||||||
|
(using [proto_header_encoding]) of the protocol-specific part
|
||||||
|
of a block header, without the signature. *)
|
||||||
|
|
||||||
val forge_unsigned:
|
val forge_unsigned:
|
||||||
Block_header.shell_header -> proto_header -> MBytes.t
|
Block_header.shell_header -> proto_header -> MBytes.t
|
||||||
(** [forge_header shell_hdr proto_hdr] is the binary serialization
|
(** [forge_header shell_hdr proto_hdr] is the binary serialization
|
||||||
|
@ -74,7 +74,8 @@ let begin_construction
|
|||||||
~predecessor_level:_
|
~predecessor_level:_
|
||||||
~predecessor_fitness:pred_fitness
|
~predecessor_fitness:pred_fitness
|
||||||
~predecessor:_
|
~predecessor:_
|
||||||
~timestamp:_ =
|
~timestamp:_
|
||||||
|
?proto_header:_ () =
|
||||||
Fitness.to_int64 pred_fitness >>=? function pred_fitness ->
|
Fitness.to_int64 pred_fitness >>=? function pred_fitness ->
|
||||||
let fitness = Int64.succ pred_fitness in
|
let fitness = Int64.succ pred_fitness in
|
||||||
return { context ; fitness }
|
return { context ; fitness }
|
||||||
|
@ -95,7 +95,8 @@ module type PROTOCOL = sig
|
|||||||
predecessor_fitness: Fitness.t ->
|
predecessor_fitness: Fitness.t ->
|
||||||
predecessor: Block_hash.t ->
|
predecessor: Block_hash.t ->
|
||||||
timestamp: Time.t ->
|
timestamp: Time.t ->
|
||||||
validation_state tzresult Lwt.t
|
?proto_header: MBytes.t ->
|
||||||
|
unit -> validation_state tzresult Lwt.t
|
||||||
|
|
||||||
(** Called after {!begin_application} (or {!begin_construction}) and
|
(** Called after {!begin_application} (or {!begin_construction}) and
|
||||||
before {!finalize_block}, with each operation in the block. *)
|
before {!finalize_block}, with each operation in the block. *)
|
||||||
|
@ -107,7 +107,9 @@ let begin_construction
|
|||||||
~predecessor_level:_
|
~predecessor_level:_
|
||||||
~predecessor_fitness:fitness
|
~predecessor_fitness:fitness
|
||||||
~predecessor:_
|
~predecessor:_
|
||||||
~timestamp:_ =
|
~timestamp:_
|
||||||
|
?proto_header:_
|
||||||
|
() =
|
||||||
(* Dummy result. *)
|
(* Dummy result. *)
|
||||||
return { Updater.message = None ; context ; fitness }
|
return { Updater.message = None ; context ; fitness }
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user