(**************************************************************************) (* *) (* Copyright (c) 2014 - 2016. *) (* Dynamic Ledger Solutions, Inc. *) (* *) (* All rights reserved. No warranty, explicit or implicit, provided. *) (* *) (**************************************************************************) (** Tezos Protocol Implementation - Main Entry Points *) open Tezos_context type error += Bad_endorsement (* TODO: doc *) type error += Insert_coin (* TODO: doc *) type error += Contract_not_delegatable (* TODO: doc *) type error += Unimplemented type error += Invalid_voting_period let apply_delegate_operation_content ctxt delegate pred_block block_priority = function | Endorsement { block ; slot } -> fail_unless (Block_hash.equal block pred_block) Bad_endorsement >>=? fun () -> Mining.check_signing_rights ctxt slot delegate >>=? fun () -> Fitness.increase ctxt >>=? fun ctxt -> Mining.pay_endorsement_bond ctxt delegate >>=? fun (ctxt, bond) -> Mining.endorsement_reward ~block_priority >>=? fun reward -> Level.current ctxt >>=? fun { cycle = current_cycle } -> Lwt.return Tez.(reward +? bond) >>=? fun full_reward -> Reward.record ctxt delegate current_cycle full_reward | Proposals { period ; proposals } -> Level.current ctxt >>=? fun level -> fail_unless Voting_period.(level.voting_period = period) Invalid_voting_period >>=? fun () -> Amendment.record_proposals ctxt delegate proposals | Ballot { period ; proposal ; ballot } -> Level.current ctxt >>=? fun level -> fail_unless Voting_period.(level.voting_period = period) Invalid_voting_period >>=? fun () -> Amendment.record_ballot ctxt delegate proposal ballot let rec is_reject = function | [] -> false | Script_interpreter.Reject _ :: _ -> true | _ :: err -> is_reject err type error += Non_scripted_contract_with_parameter type error += Scripted_contract_without_paramater let apply_manager_operation_content ctxt accept_failing_script source = function | 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 | No_script -> begin match parameters with | None | Some (Prim (_, "void", [])) -> return ctxt | Some _ -> fail Non_scripted_contract_with_parameter end | Script { code ; storage } -> match parameters with | None -> fail Scripted_contract_without_paramater | Some parameters -> Script_interpreter.execute source destination ctxt storage code amount parameters (Constants.instructions_per_transaction ctxt) >>= function | Ok (storage_res, _res, _steps, ctxt) -> (* TODO: pay for the steps and the storage diff: update_script_storage checks the storage cost *) Contract.update_script_storage ctxt destination storage_res >>=? fun ctxt -> return ctxt | Error err -> if accept_failing_script && is_reject err then return ctxt else Lwt.return (Error err) end | Origination { manager ; delegate ; script ; spendable ; delegatable ; credit } -> begin match script with | No_script -> return () | Script { code ; storage } -> Script_ir_translator.parse_script ctxt storage code >>=? fun _ -> let storage_fee = Script.storage_cost storage in let code_fee = Script.code_cost code in Lwt.return Tez.(code_fee +? storage_fee) >>=? fun script_fee -> Lwt.return Tez.(script_fee +? Constants.origination_burn) >>=? fun total_fee -> fail_unless Tez.(credit > total_fee) Insert_coin >>=? fun () -> return () end >>=? fun () -> Contract.spend ctxt source credit >>=? fun ctxt -> Lwt.return Tez.(credit -? Constants.origination_burn) >>=? fun balance -> Contract.originate ctxt ~manager ~delegate ~balance ~script ~spendable ~delegatable >>=? fun (ctxt, _) -> return ctxt | Issuance { asset = (asset, key); amount } -> Contract.issue ctxt source asset key amount (* TODO: pay for the storage diff *) | Delegation delegate -> Contract.is_delegatable ctxt source >>=? fun delegatable -> fail_unless delegatable Contract_not_delegatable >>=? fun () -> Contract.set_delegate ctxt source delegate let check_signature_and_update_public_key ctxt id public_key op = begin match public_key with | None -> return ctxt | Some public_key -> Public_key.set ctxt id public_key end >>=? fun ctxt -> Public_key.get ctxt id >>=? fun public_key -> Operation.check_signature public_key op >>=? fun () -> return ctxt (* TODO document parameters *) let apply_sourced_operation ctxt accept_failing_script miner_contract pred_block block_prio operation ops = match ops with | Manager_operations { source ; public_key ; fee ; counter ; operations = contents } -> 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 -> fold_left_s (fun ctxt content -> apply_manager_operation_content ctxt accept_failing_script source content) ctxt contents >>=? fun ctxt -> return ctxt | Delegate_operations { source ; operations = contents } -> let delegate = Ed25519.hash source in 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 -> return ctxt let apply_anonymous_operation ctxt miner_contract kind = match kind with | Seed_nonce_revelation { level ; nonce } -> let level = Level.from_raw ctxt level in Nonce.reveal ctxt level nonce >>=? fun (ctxt, delegate_to_reward, reward_amount) -> Reward.record ctxt delegate_to_reward level.cycle reward_amount >>=? fun ctxt -> (match miner_contract with | None -> return ctxt | Some contract -> Contract.credit ctxt contract Constants.seed_nonce_revelation_tip) let apply_operation ctxt accept_failing_script miner_contract pred_block block_prio operation = match operation.contents with | Anonymous_operations ops -> fold_left_s (fun ctxt -> apply_anonymous_operation ctxt miner_contract) ctxt ops | Sourced_operations op -> apply_sourced_operation ctxt accept_failing_script miner_contract pred_block block_prio operation op let may_start_new_cycle ctxt = Mining.dawn_of_a_new_cycle ctxt >>=? function | None -> return ctxt | Some new_cycle -> let last_cycle = match Cycle.pred new_cycle with | None -> assert false | Some last_cycle -> last_cycle in 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 -> Roll.froze_rolls_for_cycle ctxt (Cycle.succ new_cycle) >>=? fun ctxt -> Timestamp.get_current ctxt >>=? fun timestamp -> 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 let apply_main ctxt accept_failing_script block_header operations = (* read only checks *) 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 >>=? fun delegate_pkh -> Mining.check_signature ctxt block_header delegate_pkh >>=? fun () -> (* automatic bonds payment *) Mining.pay_mining_bond ctxt block_header delegate_pkh >>=? fun ctxt -> (* set timestamp *) Timestamp.set_current ctxt block_header.shell.timestamp >>=? fun ctxt -> (* do effectful stuff *) Fitness.increase ctxt >>=? fun ctxt -> let priority = snd block_header.proto.mining_slot in fold_left_s (fun ctxt operation -> apply_operation ctxt accept_failing_script (Some (Contract.default_contract delegate_pkh)) block_header.shell.predecessor priority operation) ctxt operations >>=? fun ctxt -> (* end of level (from this point nothing should fail) *) let reward = Mining.base_mining_reward ctxt ~priority:(snd block_header.proto.mining_slot) in Nonce.record_hash ctxt delegate_pkh reward block_header.proto.seed_nonce_hash >>=? fun ctxt -> Reward.pay_due_rewards ctxt >>=? fun ctxt -> Level.increment_current ctxt >>=? fun ctxt -> (* end of cycle *) may_start_new_cycle ctxt >>=? fun ctxt -> Amendment.may_start_new_voting_cycle ctxt >>=? fun ctxt -> return ctxt type error += Internal_error of string let apply ctxt accept_failing_script block_header operations = (init ctxt >>=? fun ctxt -> get_prevalidation ctxt >>= function | true -> fail (Internal_error "we should not call `apply` after `preapply`!") | false -> apply_main ctxt accept_failing_script block_header operations >>=? fun ctxt -> finalize ctxt) let empty_result = { Updater.applied = []; refused = Operation_hash_map.empty; branch_refused = Operation_hash_map.empty; branch_delayed = Operation_hash_map.empty; } let compare_operations op1 op2 = match op1.contents, op2.contents with | Anonymous_operations _, Anonymous_operations _ -> 0 | Anonymous_operations _, Sourced_operations _ -> 1 | Sourced_operations _, Anonymous_operations _ -> -1 | Sourced_operations op1, Sourced_operations op2 -> match op1, op2 with | Delegate_operations _, Manager_operations _ -> -1 | Manager_operations _, Delegate_operations _ -> 1 | Delegate_operations _, Delegate_operations _ -> 0 | Manager_operations op1, Manager_operations op2 -> begin (* Manager operations with smaller counter are pre-validated first. *) Int32.compare op1.counter op2.counter end let merge_result r r' = let open Updater in let merge _key a b = match a, b with | None, None -> None | Some x, None -> Some x | _, Some y -> Some y in { applied = r.applied @ r'.applied ; refused = Operation_hash_map.merge merge r.refused r'.refused ; branch_refused = Operation_hash_map.merge merge r.branch_refused r'.branch_refused ; branch_delayed = r'.branch_delayed ; } let prevalidate ctxt pred_block sort operations = let operations = if sort then List.sort compare_operations operations else operations in let rec loop ctxt operations = (Lwt_list.fold_left_s (fun (ctxt, r) op -> apply_operation ctxt false None pred_block 0l op >>= function | Ok ctxt -> let applied = op.hash :: r.Updater.applied in Lwt.return (ctxt, { r with Updater.applied} ) | Error errors -> match classify_errors errors with | `Branch -> let branch_refused = Operation_hash_map.add op.hash errors r.Updater.branch_refused in Lwt.return (ctxt, { r with Updater.branch_refused }) | `Permanent -> let refused = Operation_hash_map.add op.hash errors r.Updater.refused in Lwt.return (ctxt, { r with Updater.refused }) | `Temporary -> let branch_delayed = Operation_hash_map.add op.hash errors r.Updater.branch_delayed in Lwt.return (ctxt, { r with Updater.branch_delayed })) (ctxt, empty_result) operations >>= fun (ctxt, r) -> match r.Updater.applied with | _ :: _ when sort -> let rechecked_operations = List.filter (fun op -> Operation_hash_map.mem op.hash r.Updater.branch_delayed) operations in loop ctxt rechecked_operations >>=? fun (ctxt, r') -> return (ctxt, merge_result r r') | _ -> return (ctxt, r)) in loop ctxt operations let preapply ctxt pred_block timestamp sort operations = let result = init ctxt >>=? fun ctxt -> begin get_prevalidation ctxt >>= function | true -> return ctxt | false -> set_prevalidation ctxt >>= fun ctxt -> Fitness.increase ctxt >>=? fun ctxt -> return ctxt end >>=? fun ctxt -> Timestamp.set_current ctxt timestamp >>=? fun ctxt -> prevalidate ctxt pred_block sort operations >>=? fun (ctxt, r) -> (* TODO should accept failing script in the last round ? or: what should we export to let the miner decide *) finalize ctxt >>=? fun ctxt -> return (ctxt, r) in (* "Reify" errors into options. *) result >>|? function (ctxt, r) -> (ctxt, { r with Updater.applied = List.rev r.Updater.applied })