From d46aaecdfc1d3d4e0485def9bb753cc237dbe51e Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 22 Oct 2020 09:42:27 +0200 Subject: [PATCH 1/6] Proto: Cut by 2 the number of operations in gas update and check Signed-off-by: Yann Regis-Gianas --- .../lib_protocol/gas_limit_repr.ml | 32 +++-- .../lib_protocol/gas_limit_repr.mli | 7 +- src/proto_alpha/lib_protocol/raw_context.ml | 122 +++++++++++++++--- 3 files changed, 127 insertions(+), 34 deletions(-) diff --git a/src/proto_alpha/lib_protocol/gas_limit_repr.ml b/src/proto_alpha/lib_protocol/gas_limit_repr.ml index d1fd63281d4f..079d14adb1ee 100644 --- a/src/proto_alpha/lib_protocol/gas_limit_repr.ml +++ b/src/proto_alpha/lib_protocol/gas_limit_repr.ml @@ -79,23 +79,21 @@ let byte_written_weight = Z.of_int (scaling_factor * 15) let cost_to_milligas (cost : cost) : Arith.fp = Arith.unsafe_fp cost -let raw_consume block_gas operation_gas cost = - match operation_gas with - | Unaccounted -> - ok (block_gas, Unaccounted) - | Limited {remaining} -> - let gas = cost_to_milligas cost in - if Arith.(gas > zero) then - let remaining = Arith.sub remaining gas in - let block_remaining = Arith.sub block_gas gas in - if Arith.(remaining < zero) then error Operation_quota_exceeded - else if Arith.(block_remaining < zero) then error Block_quota_exceeded - else ok (block_remaining, Limited {remaining}) - else ok (block_gas, operation_gas) - -let raw_check_enough block_gas operation_gas cost = - raw_consume block_gas operation_gas cost - >|? fun (_block_remaining, _remaining) -> () +let raw_consume gas_counter cost = + let gas = cost_to_milligas cost in + let remaining = Arith.sub gas_counter gas in + if Arith.(remaining < zero) then None else Some remaining + +let gas_exhausted_error ~count_block_gas = + if count_block_gas then error Block_quota_exceeded + else error Operation_quota_exceeded + +let raw_check_enough gas_counter ~count_block_gas cost = + match raw_consume gas_counter cost with + | None -> + gas_exhausted_error ~count_block_gas + | Some _ -> + Ok () let alloc_cost n = Z.mul allocation_weight (Z.succ n) diff --git a/src/proto_alpha/lib_protocol/gas_limit_repr.mli b/src/proto_alpha/lib_protocol/gas_limit_repr.mli index d4668cae0ee8..4e78e80ad601 100644 --- a/src/proto_alpha/lib_protocol/gas_limit_repr.mli +++ b/src/proto_alpha/lib_protocol/gas_limit_repr.mli @@ -41,9 +41,12 @@ type error += Block_quota_exceeded (* `Temporary *) type error += Operation_quota_exceeded (* `Temporary *) -val raw_consume : Arith.fp -> t -> cost -> (Arith.fp * t) tzresult +val raw_consume : Arith.fp -> cost -> Arith.fp option -val raw_check_enough : Arith.fp -> t -> cost -> unit tzresult +val gas_exhausted_error : count_block_gas:bool -> 'a tzresult + +val raw_check_enough : + Arith.fp -> count_block_gas:bool -> cost -> unit tzresult val free : cost diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index a829c1f7648e..dc8d60a6692e 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -25,6 +25,35 @@ module Int_set = Set.Make (Compare.Int) +(* + + Gas levels maintainance + ======================= + + The context maintains two levels of gas, one corresponds to the gas + available for the current operation while the other is the gas + available for the current block. + + When gas is consumed, we must morally decrement these two levels to + check if one of them hits zero. However, since these decrements are + the same on both levels, it is not strictly necessary to update the + two levels: we can simply maintain the minimum of both levels in a + [gas_counter]. The meaning of [gas_counter] is denoted by + [gas_counter_status], it can be: + + - [Unlimited] when the operation gas is unaccounted. + - [CountingOperationGas] when the operation gas level is the minimum. + - [CountingBlockGas] when the block gas level is the minimum. + + In each case, we keep enough information in [gas_counter_status] to + reconstruct the level that is not represented by [gas_counter]. + + [Raw_context] interface provides two accessors for the operation + gas level and the block gas level. These accessors compute these values + on-the-fly based on the current value of [gas_counter] and + [gas_counter_status]. + +*) type t = { context : Context.t; constants : Constants_repr.parametric; @@ -39,16 +68,24 @@ type t = { (Signature.Public_key.t * int list * bool) Signature.Public_key_hash.Map.t; fees : Tez_repr.t; rewards : Tez_repr.t; - block_gas : Gas_limit_repr.Arith.fp; - operation_gas : Gas_limit_repr.t; storage_space_to_pay : Z.t option; allocated_contracts : int option; origination_nonce : Contract_repr.origination_nonce option; temporary_lazy_storage_ids : Lazy_storage_kind.Temp_ids.t; internal_nonce : int; internal_nonces_used : Int_set.t; + gas_counter : Gas_limit_repr.Arith.fp; + gas_counter_status : gas_counter_status; } +and gas_counter_status = + | UnlimitedOperationGas of {block_gas : Gas_limit_repr.Arith.fp} + | CountingOperationGas of {block_gas_delta : Gas_limit_repr.Arith.fp} + | CountingBlockGas of {operation_gas_delta : Gas_limit_repr.Arith.fp} + +let update_gas_counter_status ctxt gas_counter_status = + {ctxt with gas_counter_status} + type context = t type root_context = t @@ -209,6 +246,26 @@ let () = (function Gas_limit_too_high -> Some () | _ -> None) (fun () -> Gas_limit_too_high) +let gas_level ctxt = + let open Gas_limit_repr in + match ctxt.gas_counter_status with + | UnlimitedOperationGas _ -> + Unaccounted + | CountingBlockGas {operation_gas_delta} -> + Limited {remaining = Arith.(add ctxt.gas_counter operation_gas_delta)} + | CountingOperationGas _ -> + Limited {remaining = ctxt.gas_counter} + +let block_gas_level ctxt = + let open Gas_limit_repr in + match ctxt.gas_counter_status with + | UnlimitedOperationGas {block_gas} -> + block_gas + | CountingBlockGas _ -> + ctxt.gas_counter + | CountingOperationGas {block_gas_delta} -> + Arith.(add ctxt.gas_counter block_gas_delta) + let check_gas_limit ctxt (remaining : 'a Gas_limit_repr.Arith.t) = if Gas_limit_repr.Arith.( @@ -218,21 +275,51 @@ let check_gas_limit ctxt (remaining : 'a Gas_limit_repr.Arith.t) = else ok_unit let set_gas_limit ctxt (remaining : 'a Gas_limit_repr.Arith.t) = - let remaining = Gas_limit_repr.Arith.fp remaining in - {ctxt with operation_gas = Limited {remaining}} + let open Gas_limit_repr in + let remaining = Arith.fp remaining in + let block_gas = block_gas_level ctxt in + let (gas_counter_status, gas_counter) = + if Arith.(remaining < block_gas) then + let block_gas_delta = Arith.sub block_gas remaining in + (CountingOperationGas {block_gas_delta}, remaining) + else + let operation_gas_delta = Arith.sub remaining block_gas in + (CountingBlockGas {operation_gas_delta}, block_gas) + in + let ctxt = update_gas_counter_status ctxt gas_counter_status in + {ctxt with gas_counter} -let set_gas_unlimited ctxt = {ctxt with operation_gas = Unaccounted} +let set_gas_unlimited ctxt = + let block_gas = block_gas_level ctxt in + update_gas_counter_status ctxt (UnlimitedOperationGas {block_gas}) -let consume_gas ctxt cost = - Gas_limit_repr.raw_consume ctxt.block_gas ctxt.operation_gas cost - >>? fun (block_gas, operation_gas) -> ok {ctxt with block_gas; operation_gas} +let is_gas_unlimited ctxt = + match ctxt.gas_counter_status with + | UnlimitedOperationGas _ -> + true + | _ -> + false -let check_enough_gas ctxt cost = - Gas_limit_repr.raw_check_enough ctxt.block_gas ctxt.operation_gas cost +let is_counting_block_gas ctxt = + match ctxt.gas_counter_status with CountingBlockGas _ -> true | _ -> false -let gas_level ctxt = ctxt.operation_gas +let consume_gas ctxt cost = + if is_gas_unlimited ctxt then ok ctxt + else + match Gas_limit_repr.raw_consume ctxt.gas_counter cost with + | Some gas_counter -> + Ok {ctxt with gas_counter} + | None -> + Gas_limit_repr.gas_exhausted_error + ~count_block_gas:(is_counting_block_gas ctxt) -let block_gas_level ctxt = ctxt.block_gas +let check_enough_gas ctxt cost = + if is_gas_unlimited ctxt then ok_unit + else + Gas_limit_repr.raw_check_enough + ctxt.gas_counter + ~count_block_gas:(is_counting_block_gas ctxt) + cost let gas_consumed ~since ~until = match (gas_level since, gas_level until) with @@ -530,15 +617,20 @@ let prepare ~level ~predecessor_timestamp ~timestamp ~fitness ctxt = fees = Tez_repr.zero; rewards = Tez_repr.zero; deposits = Signature.Public_key_hash.Map.empty; - operation_gas = Unaccounted; storage_space_to_pay = None; allocated_contracts = None; - block_gas = - Gas_limit_repr.Arith.fp constants.Constants_repr.hard_gas_limit_per_block; + gas_counter = Gas_limit_repr.Arith.zero; origination_nonce = None; temporary_lazy_storage_ids = Lazy_storage_kind.Temp_ids.init; internal_nonce = 0; internal_nonces_used = Int_set.empty; + gas_counter_status = + UnlimitedOperationGas + { + block_gas = + Gas_limit_repr.Arith.fp + constants.Constants_repr.hard_gas_limit_per_block; + }; } type previous_protocol = Genesis of Parameters_repr.t | Edo_008 -- GitLab From 2bcffcef7450c47992f9eb07320e6085be9a0f09 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Mon, 9 Nov 2020 14:45:18 +0100 Subject: [PATCH 2/6] Proto: Move errors related to gas exhaustion at the context level Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/alpha_context.ml | 5 +++ .../lib_protocol/gas_limit_repr.ml | 38 ----------------- .../lib_protocol/gas_limit_repr.mli | 9 ---- src/proto_alpha/lib_protocol/raw_context.ml | 42 ++++++++++++++----- src/proto_alpha/lib_protocol/raw_context.mli | 10 ++++- .../lib_protocol/storage_functors.ml | 8 ++++ 6 files changed, 54 insertions(+), 58 deletions(-) diff --git a/src/proto_alpha/lib_protocol/alpha_context.ml b/src/proto_alpha/lib_protocol/alpha_context.ml index 3a70b76db070..e842dce9c15d 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.ml +++ b/src/proto_alpha/lib_protocol/alpha_context.ml @@ -126,6 +126,11 @@ module Gas = struct type error += Gas_limit_too_high = Raw_context.Gas_limit_too_high + type error += Block_quota_exceeded = Raw_context.Block_quota_exceeded + + type error += + | Operation_quota_exceeded = Raw_context.Operation_quota_exceeded + let check_limit = Raw_context.check_gas_limit let set_limit = Raw_context.set_gas_limit diff --git a/src/proto_alpha/lib_protocol/gas_limit_repr.ml b/src/proto_alpha/lib_protocol/gas_limit_repr.ml index 079d14adb1ee..54eb619aebfa 100644 --- a/src/proto_alpha/lib_protocol/gas_limit_repr.ml +++ b/src/proto_alpha/lib_protocol/gas_limit_repr.ml @@ -61,10 +61,6 @@ let cost_encoding = Data_encoding.z let pp_cost fmt z = Z.pp_print fmt z -type error += Block_quota_exceeded (* `Temporary *) - -type error += Operation_quota_exceeded (* `Temporary *) - let allocation_weight = Z.of_int (scaling_factor * 2) let step_weight = Z.of_int scaling_factor @@ -84,17 +80,6 @@ let raw_consume gas_counter cost = let remaining = Arith.sub gas_counter gas in if Arith.(remaining < zero) then None else Some remaining -let gas_exhausted_error ~count_block_gas = - if count_block_gas then error Block_quota_exceeded - else error Operation_quota_exceeded - -let raw_check_enough gas_counter ~count_block_gas cost = - match raw_consume gas_counter cost with - | None -> - gas_exhausted_error ~count_block_gas - | Some _ -> - Ok () - let alloc_cost n = Z.mul allocation_weight (Z.succ n) let alloc_bytes_cost n = alloc_cost (Z.of_int ((n + 7) / 8)) @@ -114,26 +99,3 @@ let ( +@ ) x y = Z.add x y let ( *@ ) x y = Z.mul x y let alloc_mbytes_cost n = alloc_cost (Z.of_int 12) +@ alloc_bytes_cost n - -let () = - let open Data_encoding in - register_error_kind - `Temporary - ~id:"gas_exhausted.operation" - ~title:"Gas quota exceeded for the operation" - ~description: - "A script or one of its callee took more time than the operation said \ - it would" - empty - (function Operation_quota_exceeded -> Some () | _ -> None) - (fun () -> Operation_quota_exceeded) ; - register_error_kind - `Temporary - ~id:"gas_exhausted.block" - ~title:"Gas quota exceeded for the block" - ~description: - "The sum of gas consumed by all the operations in the block exceeds the \ - hard gas limit per block" - empty - (function Block_quota_exceeded -> Some () | _ -> None) - (fun () -> Block_quota_exceeded) diff --git a/src/proto_alpha/lib_protocol/gas_limit_repr.mli b/src/proto_alpha/lib_protocol/gas_limit_repr.mli index 4e78e80ad601..a990ac587265 100644 --- a/src/proto_alpha/lib_protocol/gas_limit_repr.mli +++ b/src/proto_alpha/lib_protocol/gas_limit_repr.mli @@ -37,17 +37,8 @@ val cost_encoding : cost Data_encoding.encoding val pp_cost : Format.formatter -> cost -> unit -type error += Block_quota_exceeded (* `Temporary *) - -type error += Operation_quota_exceeded (* `Temporary *) - val raw_consume : Arith.fp -> cost -> Arith.fp option -val gas_exhausted_error : count_block_gas:bool -> 'a tzresult - -val raw_check_enough : - Arith.fp -> count_block_gas:bool -> cost -> unit tzresult - val free : cost val atomic_step_cost : Z.t -> cost diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index dc8d60a6692e..82ecc836684a 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -136,6 +136,10 @@ let included_endorsements ctxt = ctxt.included_endorsements type error += Too_many_internal_operations (* `Permanent *) +type error += Block_quota_exceeded (* `Temporary *) + +type error += Operation_quota_exceeded (* `Temporary *) + let () = let open Data_encoding in register_error_kind @@ -146,7 +150,27 @@ let () = "A transaction exceeded the hard limit of internal operations it can emit" empty (function Too_many_internal_operations -> Some () | _ -> None) - (fun () -> Too_many_internal_operations) + (fun () -> Too_many_internal_operations) ; + register_error_kind + `Temporary + ~id:"gas_exhausted.operation" + ~title:"Gas quota exceeded for the operation" + ~description: + "A script or one of its callee took more time than the operation said \ + it would" + empty + (function Operation_quota_exceeded -> Some () | _ -> None) + (fun () -> Operation_quota_exceeded) ; + register_error_kind + `Temporary + ~id:"gas_exhausted.block" + ~title:"Gas quota exceeded for the block" + ~description: + "The sum of gas consumed by all the operations in the block exceeds the \ + hard gas limit per block" + empty + (function Block_quota_exceeded -> Some () | _ -> None) + (fun () -> Block_quota_exceeded) let fresh_internal_nonce ctxt = if Compare.Int.(ctxt.internal_nonce >= 65_535) then @@ -310,16 +334,10 @@ let consume_gas ctxt cost = | Some gas_counter -> Ok {ctxt with gas_counter} | None -> - Gas_limit_repr.gas_exhausted_error - ~count_block_gas:(is_counting_block_gas ctxt) + if is_counting_block_gas ctxt then error Block_quota_exceeded + else error Operation_quota_exceeded -let check_enough_gas ctxt cost = - if is_gas_unlimited ctxt then ok_unit - else - Gas_limit_repr.raw_check_enough - ctxt.gas_counter - ~count_block_gas:(is_counting_block_gas ctxt) - cost +let check_enough_gas ctxt cost = consume_gas ctxt cost >>? fun _ -> ok_unit let gas_consumed ~since ~until = match (gas_level since, gas_level until) with @@ -727,6 +745,10 @@ module type T = sig val absolute_key : context -> key -> key + type error += Block_quota_exceeded + + type error += Operation_quota_exceeded + val consume_gas : context -> Gas_limit_repr.cost -> context tzresult val check_enough_gas : context -> Gas_limit_repr.cost -> unit tzresult diff --git a/src/proto_alpha/lib_protocol/raw_context.mli b/src/proto_alpha/lib_protocol/raw_context.mli index 27c22851ddad..de829aba5d4b 100644 --- a/src/proto_alpha/lib_protocol/raw_context.mli +++ b/src/proto_alpha/lib_protocol/raw_context.mli @@ -236,8 +236,16 @@ module type T = sig from partial key relative a view. *) val absolute_key : context -> key -> key + (** Raised if block gas quota is exhausted during gas + consumption. *) + type error += Block_quota_exceeded + + (** Raised if operation gas quota is exhausted during gas + consumption. *) + type error += Operation_quota_exceeded + (** Internally used in {!Storage_functors} to consume gas from - within a view. *) + within a view. May raise {!Block_quota_exceeded} or {!Operation_quota_exceeded}. *) val consume_gas : context -> Gas_limit_repr.cost -> context tzresult (** Check if consume_gas will fail *) diff --git a/src/proto_alpha/lib_protocol/storage_functors.ml b/src/proto_alpha/lib_protocol/storage_functors.ml index 77f8d6cd16e2..132d0d4bde19 100644 --- a/src/proto_alpha/lib_protocol/storage_functors.ml +++ b/src/proto_alpha/lib_protocol/storage_functors.ml @@ -115,6 +115,10 @@ module Make_subcontext (R : REGISTER) (C : Raw_context.T) (N : NAME) : let absolute_key c k = C.absolute_key c (to_key k) + type error += Block_quota_exceeded = C.Block_quota_exceeded + + type error += Operation_quota_exceeded = C.Operation_quota_exceeded + let consume_gas = C.consume_gas let check_enough_gas = C.check_enough_gas @@ -771,6 +775,10 @@ module Make_indexed_subcontext (C : Raw_context.T) (I : INDEX) : let (t, i) = unpack c in C.absolute_key t (to_key i k) + type error += Block_quota_exceeded = C.Block_quota_exceeded + + type error += Operation_quota_exceeded = C.Operation_quota_exceeded + let consume_gas c g = let (t, i) = unpack c in C.consume_gas t g >>? fun t -> ok (pack t i) -- GitLab From 950224bac03105ae70f2313b99e856bb644d9688 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Mon, 9 Nov 2020 18:07:15 +0100 Subject: [PATCH 3/6] Proto: When operation gas is unlimited, use gas_counter for block_gas Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/raw_context.ml | 27 +++++++++------------ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index 82ecc836684a..de49511d5703 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -46,7 +46,8 @@ module Int_set = Set.Make (Compare.Int) - [CountingBlockGas] when the block gas level is the minimum. In each case, we keep enough information in [gas_counter_status] to - reconstruct the level that is not represented by [gas_counter]. + reconstruct the level that is not represented by [gas_counter]. In + the gas [Unlimited], the block gas level is stored in [gas_counter]. [Raw_context] interface provides two accessors for the operation gas level and the block gas level. These accessors compute these values @@ -79,7 +80,7 @@ type t = { } and gas_counter_status = - | UnlimitedOperationGas of {block_gas : Gas_limit_repr.Arith.fp} + | UnlimitedOperationGas | CountingOperationGas of {block_gas_delta : Gas_limit_repr.Arith.fp} | CountingBlockGas of {operation_gas_delta : Gas_limit_repr.Arith.fp} @@ -273,7 +274,7 @@ let () = let gas_level ctxt = let open Gas_limit_repr in match ctxt.gas_counter_status with - | UnlimitedOperationGas _ -> + | UnlimitedOperationGas -> Unaccounted | CountingBlockGas {operation_gas_delta} -> Limited {remaining = Arith.(add ctxt.gas_counter operation_gas_delta)} @@ -283,9 +284,7 @@ let gas_level ctxt = let block_gas_level ctxt = let open Gas_limit_repr in match ctxt.gas_counter_status with - | UnlimitedOperationGas {block_gas} -> - block_gas - | CountingBlockGas _ -> + | UnlimitedOperationGas | CountingBlockGas _ -> ctxt.gas_counter | CountingOperationGas {block_gas_delta} -> Arith.(add ctxt.gas_counter block_gas_delta) @@ -315,11 +314,12 @@ let set_gas_limit ctxt (remaining : 'a Gas_limit_repr.Arith.t) = let set_gas_unlimited ctxt = let block_gas = block_gas_level ctxt in - update_gas_counter_status ctxt (UnlimitedOperationGas {block_gas}) + let ctxt = {ctxt with gas_counter = block_gas} in + update_gas_counter_status ctxt UnlimitedOperationGas let is_gas_unlimited ctxt = match ctxt.gas_counter_status with - | UnlimitedOperationGas _ -> + | UnlimitedOperationGas -> true | _ -> false @@ -637,18 +637,13 @@ let prepare ~level ~predecessor_timestamp ~timestamp ~fitness ctxt = deposits = Signature.Public_key_hash.Map.empty; storage_space_to_pay = None; allocated_contracts = None; - gas_counter = Gas_limit_repr.Arith.zero; + gas_counter = + Gas_limit_repr.Arith.fp constants.Constants_repr.hard_gas_limit_per_block; origination_nonce = None; temporary_lazy_storage_ids = Lazy_storage_kind.Temp_ids.init; internal_nonce = 0; internal_nonces_used = Int_set.empty; - gas_counter_status = - UnlimitedOperationGas - { - block_gas = - Gas_limit_repr.Arith.fp - constants.Constants_repr.hard_gas_limit_per_block; - }; + gas_counter_status = UnlimitedOperationGas; } type previous_protocol = Genesis of Parameters_repr.t | Edo_008 -- GitLab From 7221cf25b6fc05d09ec3060a40b85b616dc95b31 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Mon, 23 Nov 2020 09:06:54 +0100 Subject: [PATCH 4/6] Proto: Untangle mutually recursive definitions to make coo happy Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/raw_context.ml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index de49511d5703..42dc652ad7f5 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -39,15 +39,21 @@ module Int_set = Set.Make (Compare.Int) the same on both levels, it is not strictly necessary to update the two levels: we can simply maintain the minimum of both levels in a [gas_counter]. The meaning of [gas_counter] is denoted by - [gas_counter_status], it can be: + [gas_counter_status]: *) - - [Unlimited] when the operation gas is unaccounted. - - [CountingOperationGas] when the operation gas level is the minimum. - - [CountingBlockGas] when the block gas level is the minimum. +type gas_counter_status = + (* When the operation gas is unaccounted: *) + | UnlimitedOperationGas + (* When the operation gas level is the minimum: *) + | CountingOperationGas of {block_gas_delta : Gas_limit_repr.Arith.fp} + (* When the block gas level is the minimum. *) + | CountingBlockGas of {operation_gas_delta : Gas_limit_repr.Arith.fp} +(* In each case, we keep enough information in [gas_counter_status] to reconstruct the level that is not represented by [gas_counter]. In - the gas [Unlimited], the block gas level is stored in [gas_counter]. + the gas [UnlimitedOperationGas], the block gas level is stored + in [gas_counter]. [Raw_context] interface provides two accessors for the operation gas level and the block gas level. These accessors compute these values @@ -79,11 +85,6 @@ type t = { gas_counter_status : gas_counter_status; } -and gas_counter_status = - | UnlimitedOperationGas - | CountingOperationGas of {block_gas_delta : Gas_limit_repr.Arith.fp} - | CountingBlockGas of {operation_gas_delta : Gas_limit_repr.Arith.fp} - let update_gas_counter_status ctxt gas_counter_status = {ctxt with gas_counter_status} -- GitLab From 1f6d653649dc9522976da8583447bd14013c71f6 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Mon, 23 Nov 2020 10:40:13 +0100 Subject: [PATCH 5/6] Proto: Add unit tests for gas levels monitoring Signed-off-by: Yann Regis-Gianas --- .../lib_protocol/test/gas_levels.ml | 134 ++++++++++++++++++ src/proto_alpha/lib_protocol/test/main.ml | 1 + 2 files changed, 135 insertions(+) create mode 100644 src/proto_alpha/lib_protocol/test/gas_levels.ml diff --git a/src/proto_alpha/lib_protocol/test/gas_levels.ml b/src/proto_alpha/lib_protocol/test/gas_levels.ml new file mode 100644 index 000000000000..7cfef33cb085 --- /dev/null +++ b/src/proto_alpha/lib_protocol/test/gas_levels.ml @@ -0,0 +1,134 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2020 Nomadic Labs, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +open Test +open Protocol +open Raw_context + +exception Gas_levels_test_error of string + +let err x = Exn (Gas_levels_test_error x) + +let succeed x = match x with Ok _ -> true | _ -> false + +let failed x = not (succeed x) + +let dummy_context () = + Context.init 1 + >>=? fun (block, _) -> + Raw_context.prepare + ~level:Int32.zero + ~predecessor_timestamp:Time.Protocol.epoch + ~timestamp:Time.Protocol.epoch + ~fitness:[] + (block.context : Environment_context.Context.t) + >|= Environment.wrap_error + +let detect_gas_exhaustion_in_fresh_context () = + dummy_context () + >>=? fun context -> + fail_unless + (consume_gas context (Z.of_int max_int) |> succeed) + (err "In a fresh context, gas consumption is unlimited.") + +let make_context initial_operation_gas = + dummy_context () + >>=? fun context -> + return + ( Gas_limit_repr.Arith.integral_of_int initial_operation_gas + |> set_gas_limit context ) + +let detect_gas_exhaustion_when_operation_gas_hits_zero () = + make_context 10 + >>=? fun context -> + fail_unless + (consume_gas context (Z.of_int max_int) |> failed) + (err "Fail when consuming more than the remaining operation gas.") + +let detect_gas_exhaustion_when_block_gas_hits_zero () = + make_context max_int + >>=? fun context -> + fail_unless + (consume_gas context (Z.of_int max_int) |> failed) + (err "Fail when consuming more than the remaining block gas.") + +let monitor initial_operation_level gas_level expectation () = + let open Gas_limit_repr.Arith in + make_context initial_operation_level + >>=? fun context -> + fail_unless + ( match consume_gas context (Z.of_int 10000) (* in milligas. *) with + | Ok context -> + let remaining = gas_level context in + remaining = integral_of_int expectation + | _ -> + false ) + (err "Monitor operation gas at each gas consumption") + +let operation_gas_level context = + match gas_level context with + | Gas_limit_repr.Limited {remaining} -> + remaining + | _ -> + (* because this function is called after [set_gas_limit]. *) + assert false + +(* + + Monitoring runs differently depending on the minimum between the + operation gas level and the block gas level. Hence, we check that + in both situations, the gas levels are correctly reported. + +*) +let monitor_operation_gas_level = monitor 100 operation_gas_level 90 + +let monitor_operation_gas_level' = + monitor max_int operation_gas_level (max_int - 10) + +let monitor_block_gas_level = monitor 100 block_gas_level 10399990 + +let monitor_block_gas_level' = monitor max_int block_gas_level 10399990 + +let quick (what, how) = tztest what `Quick how + +let tests = + List.map + quick + [ ( "Detect gas exhaustion in fresh context", + detect_gas_exhaustion_in_fresh_context ); + ( "Detect gas exhaustion when operation gas as hits zero", + detect_gas_exhaustion_when_operation_gas_hits_zero ); + ( "Detect gas exhaustion when block gas as hits zero", + detect_gas_exhaustion_when_block_gas_hits_zero ); + ( "Each gas consumption impacts operation gas level (operation < block)", + monitor_operation_gas_level ); + ( "Each gas consumption impacts operation gas level (block < operation)", + monitor_operation_gas_level' ); + ( "Each gas consumption has an impact on block gas level (operation < \ + block)", + monitor_block_gas_level ); + ( "Each gas consumption has an impact on block gas level (block < \ + operation)", + monitor_block_gas_level' ) ] diff --git a/src/proto_alpha/lib_protocol/test/main.ml b/src/proto_alpha/lib_protocol/test/main.ml index f1f32be10d88..fcfdc965fb33 100644 --- a/src/proto_alpha/lib_protocol/test/main.ml +++ b/src/proto_alpha/lib_protocol/test/main.ml @@ -44,6 +44,7 @@ let () = ("typechecking", Typechecking.tests); ("gas properties", Gas_properties.tests); ("fixed point computation", Fixed_point.tests); + ("gas levels", Gas_levels.tests); ("gas cost functions", Gas_costs.tests); ("lazy storage diff", Lazy_storage_diff.tests); ("sapling", Test_sapling.tests); -- GitLab From a9ad70667f519bac7ab32ac743106cf633ffbed5 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 15 Dec 2020 12:57:49 +0100 Subject: [PATCH 6/6] Proto: Fix inconsistent naming of data constructors Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/raw_context.ml | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index 42dc652ad7f5..d598b4b35dd2 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -43,16 +43,16 @@ module Int_set = Set.Make (Compare.Int) type gas_counter_status = (* When the operation gas is unaccounted: *) - | UnlimitedOperationGas + | Unlimited_operation_gas (* When the operation gas level is the minimum: *) - | CountingOperationGas of {block_gas_delta : Gas_limit_repr.Arith.fp} + | Count_operation_gas of {block_gas_delta : Gas_limit_repr.Arith.fp} (* When the block gas level is the minimum. *) - | CountingBlockGas of {operation_gas_delta : Gas_limit_repr.Arith.fp} + | Count_block_gas of {operation_gas_delta : Gas_limit_repr.Arith.fp} (* In each case, we keep enough information in [gas_counter_status] to reconstruct the level that is not represented by [gas_counter]. In - the gas [UnlimitedOperationGas], the block gas level is stored + the gas [Unlimited_operation_gas], the block gas level is stored in [gas_counter]. [Raw_context] interface provides two accessors for the operation @@ -275,19 +275,19 @@ let () = let gas_level ctxt = let open Gas_limit_repr in match ctxt.gas_counter_status with - | UnlimitedOperationGas -> + | Unlimited_operation_gas -> Unaccounted - | CountingBlockGas {operation_gas_delta} -> + | Count_block_gas {operation_gas_delta} -> Limited {remaining = Arith.(add ctxt.gas_counter operation_gas_delta)} - | CountingOperationGas _ -> + | Count_operation_gas _ -> Limited {remaining = ctxt.gas_counter} let block_gas_level ctxt = let open Gas_limit_repr in match ctxt.gas_counter_status with - | UnlimitedOperationGas | CountingBlockGas _ -> + | Unlimited_operation_gas | Count_block_gas _ -> ctxt.gas_counter - | CountingOperationGas {block_gas_delta} -> + | Count_operation_gas {block_gas_delta} -> Arith.(add ctxt.gas_counter block_gas_delta) let check_gas_limit ctxt (remaining : 'a Gas_limit_repr.Arith.t) = @@ -305,10 +305,10 @@ let set_gas_limit ctxt (remaining : 'a Gas_limit_repr.Arith.t) = let (gas_counter_status, gas_counter) = if Arith.(remaining < block_gas) then let block_gas_delta = Arith.sub block_gas remaining in - (CountingOperationGas {block_gas_delta}, remaining) + (Count_operation_gas {block_gas_delta}, remaining) else let operation_gas_delta = Arith.sub remaining block_gas in - (CountingBlockGas {operation_gas_delta}, block_gas) + (Count_block_gas {operation_gas_delta}, block_gas) in let ctxt = update_gas_counter_status ctxt gas_counter_status in {ctxt with gas_counter} @@ -316,17 +316,17 @@ let set_gas_limit ctxt (remaining : 'a Gas_limit_repr.Arith.t) = let set_gas_unlimited ctxt = let block_gas = block_gas_level ctxt in let ctxt = {ctxt with gas_counter = block_gas} in - update_gas_counter_status ctxt UnlimitedOperationGas + update_gas_counter_status ctxt Unlimited_operation_gas let is_gas_unlimited ctxt = match ctxt.gas_counter_status with - | UnlimitedOperationGas -> + | Unlimited_operation_gas -> true | _ -> false let is_counting_block_gas ctxt = - match ctxt.gas_counter_status with CountingBlockGas _ -> true | _ -> false + match ctxt.gas_counter_status with Count_block_gas _ -> true | _ -> false let consume_gas ctxt cost = if is_gas_unlimited ctxt then ok ctxt @@ -644,7 +644,7 @@ let prepare ~level ~predecessor_timestamp ~timestamp ~fitness ctxt = temporary_lazy_storage_ids = Lazy_storage_kind.Temp_ids.init; internal_nonce = 0; internal_nonces_used = Int_set.empty; - gas_counter_status = UnlimitedOperationGas; + gas_counter_status = Unlimited_operation_gas; } type previous_protocol = Genesis of Parameters_repr.t | Edo_008 -- GitLab