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. *)
|
|
|
|
(* *)
|
|
|
|
(**************************************************************************)
|
|
|
|
|
|
|
|
(** Tezos Protocol Implementation - Main Entry Points *)
|
|
|
|
|
|
|
|
open Tezos_context
|
|
|
|
|
2017-03-20 21:11:43 +04:00
|
|
|
type error += Wrong_voting_period of Voting_period.t * Voting_period.t (* `Temporary *)
|
|
|
|
type error += Wrong_endorsement_predecessor of Block_hash.t * Block_hash.t (* `Temporary *)
|
2017-08-02 12:22:50 +04:00
|
|
|
type error += Bad_contract_parameter of Contract.t * Script.expr option * Script.expr option (* `Permanent *)
|
2017-03-20 21:11:43 +04:00
|
|
|
|
|
|
|
let () =
|
|
|
|
register_error_kind
|
|
|
|
`Temporary
|
|
|
|
~id:"operation.wrong_endorsement_predecessor"
|
|
|
|
~title:"Wrong endorsement predecessor"
|
|
|
|
~description:"Trying to include an endorsement in a block \
|
|
|
|
that is not the successor of the endorsed one"
|
|
|
|
~pp:(fun ppf (e, p) ->
|
|
|
|
Format.fprintf ppf "Wrong predecessor %a, expected %a"
|
|
|
|
Block_hash.pp p Block_hash.pp e)
|
|
|
|
Data_encoding.(obj2
|
|
|
|
(req "expected" Block_hash.encoding)
|
|
|
|
(req "provided" Block_hash.encoding))
|
|
|
|
(function Wrong_endorsement_predecessor (e, p) -> Some (e, p) | _ -> None)
|
|
|
|
(fun (e, p) -> Wrong_endorsement_predecessor (e, p)) ;
|
|
|
|
register_error_kind
|
|
|
|
`Temporary
|
|
|
|
~id:"operation.wrong_voting_period"
|
|
|
|
~title:"Wrong voting period"
|
|
|
|
~description:"Trying to onclude a proposal or ballot \
|
|
|
|
meant for another voting period"
|
|
|
|
~pp:(fun ppf (e, p) ->
|
|
|
|
Format.fprintf ppf "Wrong voting period %a, current is %a"
|
|
|
|
Voting_period.pp p Voting_period.pp e)
|
|
|
|
Data_encoding.(obj2
|
|
|
|
(req "current" Voting_period.encoding)
|
|
|
|
(req "provided" Voting_period.encoding))
|
|
|
|
(function Wrong_voting_period (e, p) -> Some (e, p) | _ -> None)
|
2017-08-02 12:22:50 +04:00
|
|
|
(fun (e, p) -> Wrong_voting_period (e, p));
|
|
|
|
register_error_kind
|
|
|
|
`Permanent
|
|
|
|
~id:"badContractParameter"
|
|
|
|
~title:"Contract supplied an invalid parameter"
|
|
|
|
~description:"Either no parameter was supplied to a contract, \
|
|
|
|
a parameter was passed to an account, \
|
|
|
|
or a parameter was supplied of the wrong type"
|
|
|
|
Data_encoding.(obj3
|
|
|
|
(req "contract" Contract.encoding)
|
|
|
|
(opt "expectedType" Script.expr_encoding)
|
|
|
|
(opt "providedArgument" Script.expr_encoding))
|
|
|
|
(function Bad_contract_parameter (c, expected, supplied) ->
|
|
|
|
Some (c, expected, supplied) | _ -> None)
|
|
|
|
(fun (c, expected, supplied) -> Bad_contract_parameter (c, expected, supplied))
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let apply_delegate_operation_content
|
|
|
|
ctxt delegate pred_block block_priority = function
|
|
|
|
| Endorsement { block ; slot } ->
|
|
|
|
fail_unless
|
2017-03-20 21:11:43 +04:00
|
|
|
(Block_hash.equal block pred_block)
|
|
|
|
(Wrong_endorsement_predecessor (pred_block, block)) >>=? fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Mining.check_signing_rights ctxt slot delegate >>=? fun () ->
|
2017-04-10 14:14:11 +04:00
|
|
|
let ctxt = Fitness.increase ctxt in
|
2016-09-08 21:13:10 +04:00
|
|
|
Mining.pay_endorsement_bond ctxt delegate >>=? fun (ctxt, bond) ->
|
|
|
|
Mining.endorsement_reward ~block_priority >>=? fun reward ->
|
2017-04-10 15:01:22 +04:00
|
|
|
let { cycle = current_cycle } : Level.t = Level.current ctxt in
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.return Tez.(reward +? bond) >>=? fun full_reward ->
|
|
|
|
Reward.record ctxt delegate current_cycle full_reward
|
|
|
|
| Proposals { period ; proposals } ->
|
2017-04-10 15:01:22 +04:00
|
|
|
let level = Level.current ctxt in
|
2016-09-08 21:13:10 +04:00
|
|
|
fail_unless Voting_period.(level.voting_period = period)
|
2017-03-20 21:11:43 +04:00
|
|
|
(Wrong_voting_period (level.voting_period, period)) >>=? fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Amendment.record_proposals ctxt delegate proposals
|
|
|
|
| Ballot { period ; proposal ; ballot } ->
|
2017-04-10 15:01:22 +04:00
|
|
|
let level = Level.current ctxt in
|
2016-09-08 21:13:10 +04:00
|
|
|
fail_unless Voting_period.(level.voting_period = period)
|
2017-03-20 21:11:43 +04:00
|
|
|
(Wrong_voting_period (level.voting_period, period)) >>=? fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Amendment.record_ballot ctxt delegate proposal ballot
|
|
|
|
|
2016-10-20 20:54:16 +04:00
|
|
|
let apply_manager_operation_content
|
|
|
|
ctxt origination_nonce source = function
|
2016-09-08 21:13:10 +04:00
|
|
|
| Transaction { amount ; parameters ; destination } -> begin
|
|
|
|
Contract.spend ctxt source amount >>=? fun ctxt ->
|
|
|
|
Contract.credit ctxt destination amount >>=? fun ctxt ->
|
|
|
|
Contract.get_script ctxt destination >>=? function
|
2017-03-09 22:17:13 +04:00
|
|
|
| None -> begin
|
2016-09-08 21:13:10 +04:00
|
|
|
match parameters with
|
2017-06-15 01:35:24 +04:00
|
|
|
| None | Some (Prim (_, "Unit", [], _)) ->
|
2016-10-20 20:54:16 +04:00
|
|
|
return (ctxt, origination_nonce, None)
|
2017-08-02 12:22:50 +04:00
|
|
|
| Some _ -> fail (Bad_contract_parameter (destination, None, parameters))
|
2016-09-08 21:13:10 +04:00
|
|
|
end
|
2017-03-09 22:17:13 +04:00
|
|
|
| Some { code ; storage } ->
|
2017-08-02 12:22:50 +04:00
|
|
|
let call_contract argument =
|
|
|
|
Script_interpreter.execute
|
|
|
|
origination_nonce
|
|
|
|
source destination ctxt storage code amount argument
|
|
|
|
(Constants.instructions_per_transaction ctxt)
|
|
|
|
>>= function
|
|
|
|
| Ok (storage_res, _res, _steps, ctxt, origination_nonce) ->
|
|
|
|
(* TODO: pay for the steps and the storage diff:
|
|
|
|
update_script_storage checks the storage cost *)
|
|
|
|
Contract.update_script_storage_and_fees
|
|
|
|
ctxt destination
|
|
|
|
Script_interpreter.dummy_storage_fee storage_res >>=? fun ctxt ->
|
|
|
|
return (ctxt, origination_nonce, None)
|
|
|
|
| Error err ->
|
|
|
|
return (ctxt, origination_nonce, Some err) in
|
|
|
|
match parameters, code.arg_type with
|
2017-06-15 01:35:24 +04:00
|
|
|
| None, Prim (_, "unit", _, _) -> call_contract (Prim (0, "Unit", [], None))
|
2017-08-02 12:22:50 +04:00
|
|
|
| Some parameters, arg_type -> begin
|
|
|
|
Script_ir_translator.typecheck_data ctxt (parameters, arg_type) >>= function
|
|
|
|
| Ok () -> call_contract parameters
|
|
|
|
| Error errs ->
|
|
|
|
let err = Bad_contract_parameter (destination, Some arg_type, Some parameters) in
|
|
|
|
return (ctxt, origination_nonce, Some ((err :: errs)))
|
|
|
|
end
|
|
|
|
| None, arg_type -> fail (Bad_contract_parameter (destination, Some arg_type, None))
|
2016-09-08 21:13:10 +04:00
|
|
|
end
|
|
|
|
| Origination { manager ; delegate ; script ;
|
2017-03-09 22:17:13 +04:00
|
|
|
spendable ; delegatable ; credit } ->
|
2017-06-06 21:39:46 +04:00
|
|
|
begin match script with
|
|
|
|
| None -> return None
|
|
|
|
| Some ({ Script.storage ; code } as script) ->
|
|
|
|
Script_ir_translator.parse_script ctxt storage code >>=? fun _ ->
|
|
|
|
return (Some (script, (Script_interpreter.dummy_code_fee, Script_interpreter.dummy_storage_fee)))
|
|
|
|
end >>=? fun script ->
|
2017-03-20 17:37:01 +04:00
|
|
|
Contract.spend ctxt source Constants.origination_burn >>=? fun ctxt ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Contract.spend ctxt source credit >>=? fun ctxt ->
|
|
|
|
Contract.originate ctxt
|
2017-02-16 22:01:35 +04:00
|
|
|
origination_nonce
|
2017-03-20 17:37:01 +04:00
|
|
|
~manager ~delegate ~balance:credit
|
2017-03-09 22:17:13 +04:00
|
|
|
?script
|
|
|
|
~spendable ~delegatable >>=? fun (ctxt, _, origination_nonce) ->
|
2016-10-20 20:54:16 +04:00
|
|
|
return (ctxt, origination_nonce, None)
|
2016-09-08 21:13:10 +04:00
|
|
|
| Delegation delegate ->
|
2017-02-16 22:01:35 +04:00
|
|
|
Contract.set_delegate ctxt source delegate >>=? fun ctxt ->
|
2016-10-20 20:54:16 +04:00
|
|
|
return (ctxt, origination_nonce, None)
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let check_signature_and_update_public_key ctxt id public_key op =
|
|
|
|
begin
|
|
|
|
match public_key with
|
|
|
|
| None -> return ctxt
|
|
|
|
| Some public_key ->
|
2017-03-15 21:00:27 +04:00
|
|
|
Public_key.reveal ctxt id public_key
|
2016-09-08 21:13:10 +04:00
|
|
|
end >>=? fun ctxt ->
|
|
|
|
Public_key.get ctxt id >>=? fun public_key ->
|
|
|
|
Operation.check_signature public_key op >>=? fun () ->
|
|
|
|
return ctxt
|
|
|
|
|
|
|
|
let apply_sourced_operation
|
2016-10-20 20:54:16 +04:00
|
|
|
ctxt miner_contract pred_block block_prio
|
2017-02-16 22:01:35 +04:00
|
|
|
operation origination_nonce ops =
|
2016-09-08 21:13:10 +04:00
|
|
|
match ops with
|
|
|
|
| Manager_operations { source ; public_key ; fee ; counter ; operations = contents } ->
|
2017-03-09 22:17:13 +04:00
|
|
|
Contract.must_exist ctxt source >>=? fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Contract.get_manager ctxt source >>=? fun manager ->
|
|
|
|
check_signature_and_update_public_key
|
|
|
|
ctxt manager public_key operation >>=? fun ctxt ->
|
|
|
|
Contract.check_counter_increment
|
|
|
|
ctxt source counter >>=? fun () ->
|
|
|
|
Contract.increment_counter ctxt source >>=? fun ctxt ->
|
|
|
|
Contract.spend ctxt source fee >>=? fun ctxt ->
|
|
|
|
(match miner_contract with
|
|
|
|
| None -> return ctxt
|
|
|
|
| Some contract ->
|
|
|
|
Contract.credit ctxt contract fee) >>=? fun ctxt ->
|
2016-10-20 20:54:16 +04:00
|
|
|
fold_left_s (fun (ctxt, origination_nonce, err) content ->
|
|
|
|
match err with
|
|
|
|
| Some _ -> return (ctxt, origination_nonce, err)
|
|
|
|
| None ->
|
|
|
|
Contract.must_exist ctxt source >>=? fun () ->
|
|
|
|
apply_manager_operation_content
|
|
|
|
ctxt origination_nonce source content)
|
|
|
|
(ctxt, origination_nonce, None) contents
|
2016-09-08 21:13:10 +04:00
|
|
|
| Delegate_operations { source ; operations = contents } ->
|
2017-02-28 05:56:40 +04:00
|
|
|
let delegate = Ed25519.Public_key.hash source in
|
2016-09-08 21:13:10 +04:00
|
|
|
check_signature_and_update_public_key
|
|
|
|
ctxt delegate (Some source) operation >>=? fun ctxt ->
|
|
|
|
(* TODO, see how to extract the public key hash after this operation to
|
|
|
|
pass it to apply_delegate_operation_content *)
|
|
|
|
fold_left_s (fun ctxt content ->
|
|
|
|
apply_delegate_operation_content
|
|
|
|
ctxt delegate pred_block block_prio content)
|
|
|
|
ctxt contents >>=? fun ctxt ->
|
2016-10-20 20:54:16 +04:00
|
|
|
return (ctxt, origination_nonce, None)
|
2017-02-27 21:24:26 +04:00
|
|
|
| Dictator_operation (Activate hash) ->
|
|
|
|
let dictator_pubkey = Constants.dictator_pubkey ctxt in
|
|
|
|
Operation.check_signature dictator_pubkey operation >>=? fun () ->
|
|
|
|
activate ctxt hash >>= fun ctxt ->
|
2016-10-20 20:54:16 +04:00
|
|
|
return (ctxt, origination_nonce, None)
|
2017-02-27 21:24:26 +04:00
|
|
|
| Dictator_operation (Activate_testnet hash) ->
|
|
|
|
let dictator_pubkey = Constants.dictator_pubkey ctxt in
|
|
|
|
Operation.check_signature dictator_pubkey operation >>=? fun () ->
|
2017-04-10 23:14:17 +04:00
|
|
|
let expiration = (* in two days maximum... *)
|
|
|
|
Time.add (Timestamp.current ctxt) (Int64.mul 48L 3600L) in
|
|
|
|
fork_test_network ctxt hash expiration >>= fun ctxt ->
|
2016-10-20 20:54:16 +04:00
|
|
|
return (ctxt, origination_nonce, None)
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-02-28 05:48:51 +04:00
|
|
|
let apply_anonymous_operation ctxt miner_contract origination_nonce kind =
|
2016-09-08 21:13:10 +04:00
|
|
|
match kind with
|
|
|
|
| Seed_nonce_revelation { level ; nonce } ->
|
|
|
|
let level = Level.from_raw ctxt level in
|
2016-10-20 20:54:16 +04:00
|
|
|
Nonce.reveal ctxt level nonce
|
|
|
|
>>=? fun (ctxt, delegate_to_reward, reward_amount) ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Reward.record ctxt
|
|
|
|
delegate_to_reward level.cycle reward_amount >>=? fun ctxt ->
|
2017-02-28 05:48:51 +04:00
|
|
|
begin
|
|
|
|
match miner_contract with
|
|
|
|
| None -> return (ctxt, origination_nonce)
|
|
|
|
| Some contract ->
|
|
|
|
Contract.credit
|
|
|
|
ctxt contract Constants.seed_nonce_revelation_tip >>=? fun ctxt ->
|
|
|
|
return (ctxt, origination_nonce)
|
|
|
|
end
|
2017-06-12 13:01:25 +04:00
|
|
|
| Faucet { id = manager } ->
|
2017-02-28 05:48:51 +04:00
|
|
|
(* Free tez for all! *)
|
|
|
|
begin
|
|
|
|
match miner_contract with
|
|
|
|
| None -> return None
|
|
|
|
| Some contract -> Contract.get_delegate_opt ctxt contract
|
|
|
|
end >>=? fun delegate ->
|
|
|
|
Contract.originate ctxt
|
|
|
|
origination_nonce
|
2017-03-09 22:17:13 +04:00
|
|
|
~manager ~delegate ~balance:Constants.faucet_credit ?script:None
|
2017-02-28 05:48:51 +04:00
|
|
|
~spendable:true ~delegatable:true >>=? fun (ctxt, _, origination_nonce) ->
|
|
|
|
return (ctxt, origination_nonce)
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let apply_operation
|
2016-10-20 20:54:16 +04:00
|
|
|
ctxt miner_contract pred_block block_prio operation =
|
2016-09-08 21:13:10 +04:00
|
|
|
match operation.contents with
|
|
|
|
| Anonymous_operations ops ->
|
2017-02-28 05:48:51 +04:00
|
|
|
let origination_nonce = Contract.initial_origination_nonce operation.hash in
|
2016-09-08 21:13:10 +04:00
|
|
|
fold_left_s
|
2017-02-28 05:48:51 +04:00
|
|
|
(fun (ctxt, origination_nonce) ->
|
|
|
|
apply_anonymous_operation ctxt miner_contract origination_nonce)
|
|
|
|
(ctxt, origination_nonce) ops >>=? fun (ctxt, origination_nonce) ->
|
2016-10-20 20:54:16 +04:00
|
|
|
return (ctxt, Contract.originated_contracts origination_nonce, None)
|
2016-09-08 21:13:10 +04:00
|
|
|
| Sourced_operations op ->
|
2017-02-16 22:01:35 +04:00
|
|
|
let origination_nonce = Contract.initial_origination_nonce operation.hash in
|
2016-09-08 21:13:10 +04:00
|
|
|
apply_sourced_operation
|
2016-10-20 20:54:16 +04:00
|
|
|
ctxt miner_contract pred_block block_prio
|
|
|
|
operation origination_nonce op >>=? fun (ctxt, origination_nonce, err) ->
|
|
|
|
return (ctxt, Contract.originated_contracts origination_nonce, err)
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let may_start_new_cycle ctxt =
|
|
|
|
Mining.dawn_of_a_new_cycle ctxt >>=? function
|
|
|
|
| None -> return ctxt
|
2017-04-10 15:01:22 +04:00
|
|
|
| Some last_cycle ->
|
|
|
|
let new_cycle = Cycle.succ last_cycle in
|
2017-02-24 20:14:20 +04:00
|
|
|
Bootstrap.refill ctxt >>=? fun ctxt ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Seed.clear_cycle ctxt last_cycle >>=? fun ctxt ->
|
|
|
|
Seed.compute_for_cycle ctxt (Cycle.succ new_cycle) >>=? fun ctxt ->
|
|
|
|
Roll.clear_cycle ctxt last_cycle >>=? fun ctxt ->
|
2016-10-26 19:02:10 +04:00
|
|
|
Roll.freeze_rolls_for_cycle ctxt (Cycle.succ new_cycle) >>=? fun ctxt ->
|
2017-04-10 14:14:11 +04:00
|
|
|
let timestamp = Timestamp.current ctxt in
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.return (Timestamp.(timestamp +? (Constants.time_before_reward ctxt)))
|
|
|
|
>>=? fun reward_date ->
|
|
|
|
Reward.set_reward_time_for_cycle
|
|
|
|
ctxt last_cycle reward_date >>=? fun ctxt ->
|
|
|
|
return ctxt
|
|
|
|
|
2017-04-26 17:01:39 +04:00
|
|
|
let begin_full_construction ctxt pred_timestamp proto_header =
|
|
|
|
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_partial_construction ctxt =
|
|
|
|
let ctxt = Fitness.increase ctxt in
|
|
|
|
return ctxt
|
2016-10-20 20:54:16 +04:00
|
|
|
|
2017-04-26 17:01:39 +04:00
|
|
|
let begin_application ctxt block_header pred_timestamp =
|
|
|
|
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 ->
|
2017-04-10 14:14:11 +04:00
|
|
|
let ctxt = Fitness.increase ctxt in
|
2016-10-20 20:54:16 +04:00
|
|
|
return (ctxt, miner)
|
|
|
|
|
2017-04-26 17:01:39 +04:00
|
|
|
let finalize_application ctxt block_proto_header miner =
|
2016-09-08 21:13:10 +04:00
|
|
|
(* end of level (from this point nothing should fail) *)
|
2017-04-26 17:01:39 +04:00
|
|
|
let priority = block_proto_header.Block_header.priority in
|
2017-04-02 18:07:19 +04:00
|
|
|
let reward = Mining.base_mining_reward ctxt ~priority in
|
2016-09-08 21:13:10 +04:00
|
|
|
Nonce.record_hash ctxt
|
2017-04-26 17:01:39 +04:00
|
|
|
miner reward block_proto_header.seed_nonce_hash >>=? fun ctxt ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Reward.pay_due_rewards ctxt >>=? fun ctxt ->
|
|
|
|
(* end of cycle *)
|
|
|
|
may_start_new_cycle ctxt >>=? fun ctxt ->
|
|
|
|
Amendment.may_start_new_voting_cycle ctxt >>=? fun ctxt ->
|
2017-04-10 14:14:11 +04:00
|
|
|
return ctxt
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let compare_operations op1 op2 =
|
|
|
|
match op1.contents, op2.contents with
|
|
|
|
| Anonymous_operations _, Anonymous_operations _ -> 0
|
2017-03-02 18:45:06 +04:00
|
|
|
| Anonymous_operations _, Sourced_operations _ -> -1
|
|
|
|
| Sourced_operations _, Anonymous_operations _ -> 1
|
2016-09-08 21:13:10 +04:00
|
|
|
| Sourced_operations op1, Sourced_operations op2 ->
|
|
|
|
match op1, op2 with
|
2017-02-27 21:24:26 +04:00
|
|
|
| Delegate_operations _, (Manager_operations _ | Dictator_operation _) -> -1
|
|
|
|
| Manager_operations _, Dictator_operation _ -> -1
|
|
|
|
| Dictator_operation _, Manager_operations _ -> 1
|
|
|
|
| (Manager_operations _ | Dictator_operation _), Delegate_operations _ -> 1
|
2016-09-08 21:13:10 +04:00
|
|
|
| Delegate_operations _, Delegate_operations _ -> 0
|
2017-02-27 21:24:26 +04:00
|
|
|
| Dictator_operation _, Dictator_operation _ -> 0
|
2016-09-08 21:13:10 +04:00
|
|
|
| Manager_operations op1, Manager_operations op2 -> begin
|
|
|
|
(* Manager operations with smaller counter are pre-validated first. *)
|
|
|
|
Int32.compare op1.counter op2.counter
|
|
|
|
end
|