diff --git a/src/lib_scoru_wasm/test/test_fixed_nb_ticks.ml b/src/lib_scoru_wasm/test/test_fixed_nb_ticks.ml new file mode 100644 index 0000000000000000000000000000000000000000..2b09faee8e60f2c1faa149be7a2411099fba7625 --- /dev/null +++ b/src/lib_scoru_wasm/test/test_fixed_nb_ticks.ml @@ -0,0 +1,124 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2021 Marigold *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Testing + ------- + Component: Wasm_pvm + Invocation: dune exec src/lib_scoru_wasm/test/test_scoru_wasm.exe \ + -- test "^Max nb of ticks$" + Subject: WASM PVM evaluation tests for fixed nb of ticks per top level call +*) + +open Tztest +open Wasm_utils + +let loop_module = + {| + (module + (memory 1) + (export "mem"(memory 0)) + (func (export "kernel_next") + (loop $my_loop + br $my_loop) + ) + ) +|} + +let noop_module = + {| + (module + (memory 1) + (export "mem"(memory 0)) + (func (export "kernel_next") + nop + ) + ) +|} + +let test_looping_kernel () = + let open Lwt_result_syntax in + let max_nb_ticks = 5000L in + + (* This module loops indefinitely. *) + let*! loop_module_tree = initial_tree ~max_tick:max_nb_ticks loop_module in + let* stuck, _ = eval_until_stuck loop_module_tree in + match stuck with + | Too_many_ticks -> return_unit + | _ -> failwith "second: Unexpected stuck state!" + +let test_noop_kernel () = + let open Lwt_result_syntax in + let max_nb_ticks = 5000L in + + (* This module does a noop. *) + let*! noop_module_tree = initial_tree ~max_tick:max_nb_ticks noop_module in + let*! tree = eval_until_input_requested noop_module_tree in + let*! info = Wasm.get_info tree in + (* off-by-one introduced by Gather_floppies*) + return (assert (Z.(info.current_tick = of_int64 max_nb_ticks + Z.one))) + +let test_stuck_in_decode_kernel () = + let open Lwt_result_syntax in + let max_nb_ticks = 1L in + + (* This module does a noop. *) + let*! noop_module_tree = initial_tree ~max_tick:max_nb_ticks noop_module in + let* stuck, tree = eval_until_stuck noop_module_tree in + assert (stuck = Too_many_ticks) ; + let*! info = Wasm.get_info tree in + (* off-by-one introduced by Gather_floppies*) + return (assert (Z.(info.current_tick = of_int64 max_nb_ticks + Z.one))) + +let test_stuck_in_init_kernel () = + let open Lwt_result_syntax in + let max_nb_ticks = 1000L in + + (* This module does a noop. *) + let*! noop_module_tree = initial_tree ~max_tick:max_nb_ticks noop_module in + (* go to first Init step *) + let*! tree = eval_until_init noop_module_tree in + let*! stuck = Wasm.Internal_for_tests.is_stuck tree in + assert (stuck = None) ; + + (* set maximum to next tick and eval one more time *) + let*! info = Wasm.get_info tree in + let new_max_nb_ticks = info.current_tick in + let*! tree = Wasm.Internal_for_tests.set_max_nb_ticks new_max_nb_ticks tree in + let*! tree = Wasm.compute_step tree in + + (* Check is_stuck and current tick *) + let*! info = Wasm.get_info tree in + let*! stuck = Wasm.Internal_for_tests.is_stuck tree in + assert (stuck = Some Too_many_ticks) ; + (* off-by-one introduced by Gather_floppies*) + return (assert (Z.(info.current_tick = new_max_nb_ticks + Z.one))) + +let tests = + [ + tztest "nb of ticks limited" `Quick test_looping_kernel; + tztest "evaluation takes fixed nb of ticks" `Quick test_noop_kernel; + tztest "stuck in decode" `Quick test_stuck_in_decode_kernel; + tztest "stuck in init" `Quick test_stuck_in_init_kernel; + ] diff --git a/src/lib_scoru_wasm/test/test_init.ml b/src/lib_scoru_wasm/test/test_init.ml index d8c8574dfeb466613ed958becbe5b4174b8b226e..113442e0a26189ed27febe56aebc494be6dc085c 100644 --- a/src/lib_scoru_wasm/test/test_init.ml +++ b/src/lib_scoru_wasm/test/test_init.ml @@ -24,35 +24,8 @@ (*****************************************************************************) open Tztest -open Test_encodings_util open Tezos_scoru_wasm -module Wasm = Wasm_pvm.Make (Tree) - -let initial_tree code = - let open Lwt.Syntax in - let* empty_tree = empty_tree () in - let* code = Wasm_utils.wat2wasm code in - let boot_sector = - Data_encoding.Binary.to_string_exn - Gather_floppies.origination_message_encoding - (Gather_floppies.Complete_kernel (Bytes.of_string code)) - in - Wasm.Internal_for_tests.initial_tree_from_boot_sector ~empty_tree boot_sector - -let eval_until_stuck tree = - let open Lwt.Syntax in - let rec go counter tree = - let* tree = - Wasm.Internal_for_tests.compute_step_many ~max_steps:Int64.max_int tree - in - let* stuck = Wasm.Internal_for_tests.is_stuck tree in - match stuck with - | Some stuck -> Lwt_result.return stuck - | _ -> - if counter > 0 then go (pred counter) tree - else failwith "Failed to get stuck in time" - in - go 10000 tree +open Wasm_utils let test_memory0_export () = let open Lwt_result_syntax in @@ -60,7 +33,7 @@ let test_memory0_export () = let*! bad_module_tree = initial_tree {| (module (memory 1)) |} in - let* stuck = eval_until_stuck bad_module_tree in + let* stuck, _ = eval_until_stuck bad_module_tree in let* () = match stuck with | Init_error {explanation = Some "Module must export memory 0"; _} -> @@ -82,7 +55,7 @@ let test_memory0_export () = ) |} in - let* stuck = eval_until_stuck good_module_tree in + let* stuck, _ = eval_until_stuck good_module_tree in match stuck with | Eval_error {explanation = Some "unreachable executed"; _} -> return_unit | _ -> failwith "Unexpected stuck state!" @@ -112,7 +85,7 @@ let test_module_name_size () = (build size) in let*! bad_module_tree = initial_tree (build_module 513) in - let* stuck = eval_until_stuck bad_module_tree in + let* stuck, _ = eval_until_stuck bad_module_tree in let* () = match stuck with | Decode_error {explanation = Some "Names cannot exceed 512 bytes"; _} -> @@ -120,7 +93,7 @@ let test_module_name_size () = | _ -> failwith "Unexpected stuck state!" in let*! good_module_tree = initial_tree (build_module 512) in - let* stuck = eval_until_stuck good_module_tree in + let* stuck, _ = eval_until_stuck good_module_tree in match stuck with | Eval_error {explanation = Some "unreachable executed"; _} -> return_unit | _ -> failwith "Unexpected stuck state!" @@ -149,7 +122,7 @@ let test_imports () = let*! bad_module_tree = initial_tree (build_module bad_module_name bad_item_name) in - let* stuck = eval_until_stuck bad_module_tree in + let* stuck, _ = eval_until_stuck bad_module_tree in let* () = let expected_error = Wasm_pvm_errors.link_error @@ -164,7 +137,7 @@ let test_imports () = let*! bad_host_func_tree = initial_tree (build_module good_module_name bad_item_name) in - let* stuck = eval_until_stuck bad_host_func_tree in + let* stuck, _ = eval_until_stuck bad_host_func_tree in let* () = let expected_error = Wasm_pvm_errors.link_error @@ -179,7 +152,7 @@ let test_imports () = let*! good_module_tree = initial_tree (build_module good_module_name good_item_name) in - let* stuck = eval_until_stuck good_module_tree in + let* stuck, _ = eval_until_stuck good_module_tree in match stuck with | Eval_error {explanation = Some "unreachable executed"; _} -> return_unit | _ -> failwith "Unexpected stuck state!" diff --git a/src/lib_scoru_wasm/test/test_scoru_wasm.ml b/src/lib_scoru_wasm/test/test_scoru_wasm.ml index 11d04dd60f6d8ca355a578dcc76f4848dfc32500..04003fe352e5458cbb7230fb7de6e9d344bf4192 100644 --- a/src/lib_scoru_wasm/test/test_scoru_wasm.ml +++ b/src/lib_scoru_wasm/test/test_scoru_wasm.ml @@ -44,5 +44,6 @@ let () = ("Parser Encodings", Test_parser_encoding.tests); ("WASM PVM", Test_wasm_pvm.tests); ("Module Initialisation", Test_init.tests); + ("Max nb of ticks", Test_fixed_nb_ticks.tests); ] |> Lwt_main.run diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index 55b39ce6dfa8386728023420dbe346563caf37c1..7adb088638cf2d7a7af21994f245a28ee412f5f3 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -34,7 +34,7 @@ open Tztest open Tezos_scoru_wasm -module Wasm = Wasm_pvm.Make (Test_encodings_util.Tree) +open Wasm_utils (* Kernel failing at `kernel_next` invocation. *) let unreachable_kernel = "unreachable" @@ -87,6 +87,7 @@ let check_error expected_kind expected_reason error = It depends on the backend, if there are registered printers or not, it is not safe to rely on its string representation. *) | Some `Unknown, Unknown_error _ -> true + | Some `Too_many_ticks, Too_many_ticks -> true (* The expected step doesn't corresponds to the actual stuck step. *) | Some _, _ -> false (* No check to do, we simply assume the PVM is in a stuck state. *) @@ -96,27 +97,6 @@ let is_stuck ?step ?reason = function | Wasm_pvm.Stuck err -> check_error step reason err | _ -> false -let initial_boot_sector_from_kernel kernel = - let open Lwt_syntax in - let* empty_tree = Test_encodings_util.empty_tree () in - let origination_message = - Data_encoding.Binary.to_string_exn - Gather_floppies.origination_message_encoding - @@ Gather_floppies.Complete_kernel (String.to_bytes kernel) - in - Wasm.Internal_for_tests.initial_tree_from_boot_sector - ~empty_tree - origination_message - -let rec eval_until_input_requested ?(max_steps = Int64.max_int) tree = - let open Lwt_syntax in - let* info = Wasm.get_info tree in - match info.input_request with - | No_input_required -> - let* tree = Wasm.Internal_for_tests.compute_step_many ~max_steps tree in - eval_until_input_requested ~max_steps tree - | Input_required -> return tree - let set_input_step message message_counter tree = let input_info = Wasm_pvm_sig. @@ -131,7 +111,7 @@ let set_input_step message message_counter tree = let should_boot_unreachable_kernel ~max_steps kernel = let open Lwt_syntax in - let* tree = initial_boot_sector_from_kernel kernel in + let* tree = initial_tree ~from_binary:true kernel in (* Make the first ticks of the WASM PVM (parsing of origination message, parsing and init of the kernel), to switch it to “Input_requested” mode. *) @@ -176,7 +156,7 @@ let should_boot_unreachable_kernel ~max_steps kernel = let should_run_debug_kernel kernel = let open Lwt_syntax in - let* tree = initial_boot_sector_from_kernel kernel in + let* tree = initial_tree ~from_binary:true kernel in (* Make the first ticks of the WASM PVM (parsing of origination message, parsing and init of the kernel), to switch it to “Input_requested” mode. *) @@ -204,7 +184,7 @@ let add_value tree key_steps = let should_run_store_has_kernel kernel = let open Lwt_syntax in - let* tree = initial_boot_sector_from_kernel kernel in + let* tree = initial_tree ~from_binary:true kernel in let* tree = add_value tree ["hi"; "bye"] in let* tree = add_value tree ["hello"] in let* tree = add_value tree ["hello"; "universe"] in @@ -232,7 +212,7 @@ let should_run_store_has_kernel kernel = let should_run_store_list_size_kernel kernel = let open Lwt_syntax in - let* tree = initial_boot_sector_from_kernel kernel in + let* tree = initial_tree ~from_binary:true kernel in let* tree = add_value tree ["one"; "two"] in let* tree = add_value tree ["one"; "three"] in let* tree = add_value tree ["one"; "four"] in diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml b/src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml index f4abb551fbcf52ac909d74391419dc1128201102..ca0fa074c9f4002c10119a7b6f72902b5d2e2150 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml @@ -218,6 +218,8 @@ let pp_error_state out = function Format.fprintf out "@[Invalid_state (%s)@]" err | Wasm_pvm_errors.Unknown_error err -> Format.fprintf out "@[Unknown_error (%s)@]" err + | Wasm_pvm_errors.Too_many_ticks -> + Format.fprintf out "@[Too_many_ticks@]" let print_error_state = Format.asprintf "%a" pp_error_state @@ -235,6 +237,7 @@ let error_state_check state state' = | Invalid_state err, Invalid_state err' | Unknown_error err, Unknown_error err' -> Lwt.return_ok (err = err') + | Too_many_ticks, Too_many_ticks -> Lwt.return_ok true | _, _ -> Lwt.return_ok false let tests = diff --git a/src/lib_scoru_wasm/test/wasm_utils.ml b/src/lib_scoru_wasm/test/wasm_utils.ml index b28f454fea1d48fe696f23e4c0080976c8102a99..ea25626ce55ab5f1024f06c67679b593e2bc73ec 100644 --- a/src/lib_scoru_wasm/test/wasm_utils.ml +++ b/src/lib_scoru_wasm/test/wasm_utils.ml @@ -24,6 +24,9 @@ (*****************************************************************************) open Tezos_webassembly_interpreter +open Tezos_scoru_wasm +open Test_encodings_util +module Wasm = Wasm_pvm.Make (Tree) let parse_module code = let def = Parse.string_to_module code in @@ -34,3 +37,53 @@ let parse_module code = let wat2wasm code = let modul = parse_module code in Encode.encode modul + +let initial_tree ?(max_tick = 100000L) ?(from_binary = false) code = + let open Lwt.Syntax in + let max_tick_Z = Z.of_int64 max_tick in + let* empty_tree = empty_tree () in + let* code = if from_binary then Lwt.return code else wat2wasm code in + let boot_sector = + Data_encoding.Binary.to_string_exn + Gather_floppies.origination_message_encoding + (Gather_floppies.Complete_kernel (String.to_bytes code)) + in + let* tree = + Wasm.Internal_for_tests.initial_tree_from_boot_sector + ~empty_tree + boot_sector + in + Wasm.Internal_for_tests.set_max_nb_ticks max_tick_Z tree + +let eval_until_stuck ?(max_steps = 20000L) tree = + let open Lwt.Syntax in + let rec go counter tree = + let* tree = Wasm.Internal_for_tests.compute_step_many ~max_steps tree in + let* stuck = Wasm.Internal_for_tests.is_stuck tree in + match stuck with + | Some stuck -> Lwt_result.return (stuck, tree) + | _ -> + if counter > 0L then go (Int64.pred counter) tree + else failwith "Failed to get stuck in time" + in + go max_steps tree + +let rec eval_until_input_requested ?(max_steps = Int64.max_int) tree = + let open Lwt_syntax in + let* info = Wasm.get_info tree in + match info.input_request with + | No_input_required -> + let* tree = Wasm.Internal_for_tests.compute_step_many ~max_steps tree in + eval_until_input_requested tree + | Input_required -> return tree + +let rec eval_until_init tree = + let open Lwt_syntax in + let* state_after_first_message = + Wasm.Internal_for_tests.get_tick_state tree + in + match state_after_first_message with + | Stuck _ | Init _ -> return tree + | _ -> + let* tree = Wasm.compute_step tree in + eval_until_init tree diff --git a/src/lib_scoru_wasm/wasm_pvm.ml b/src/lib_scoru_wasm/wasm_pvm.ml index 900acbada6921e64f205155d04f744f2be53a1c6..c23e1e2aa1f0bc968a59a8c47f1fe7262f79eca1 100644 --- a/src/lib_scoru_wasm/wasm_pvm.ml +++ b/src/lib_scoru_wasm/wasm_pvm.ml @@ -31,6 +31,12 @@ let wasm_main_module_name = "main" kernel to expose a function named [kernel_next]. *) let wasm_entrypoint = "kernel_next" +(* TODO: #3590 + An appropriate number should be used, + currently 100 times the nb of ticks it takes tx_kernel to init, deposit, then withdraw + (so 100x 2 billion ticks) *) +let wasm_max_tick = Z.of_int 200_000_000_000 + module Wasm = Tezos_webassembly_interpreter type tick_state = @@ -48,6 +54,8 @@ type tick_state = | Eval of Wasm.Eval.config | Stuck of Wasm_pvm_errors.t +type computation_status = Starting | Restarting | Running | Failing + type pvm_state = { last_input_info : Wasm_pvm_sig.input_info option; (** Info about last read input. *) @@ -60,6 +68,9 @@ type pvm_state = { tick_state : tick_state; (** The current tick state. *) input_request : Wasm_pvm_sig.input_request; (** Signals whether or not the PVM needs input. *) + last_top_level_call : Z.t; + (** Last tick corresponding to a top-level call. *) + max_nb_ticks : Z.t; (** Number of ticks between top level call. *) } module Make (T : Tree_encoding.TREE) : @@ -160,7 +171,9 @@ struct module_reg, buffers, tick_state, - input_request ) -> + input_request, + last_top_level_call, + max_nb_ticks ) -> { last_input_info; current_tick; @@ -174,6 +187,8 @@ struct buffers; tick_state; input_request; + last_top_level_call; + max_nb_ticks; }) (fun { last_input_info; @@ -183,6 +198,8 @@ struct buffers; tick_state; input_request; + last_top_level_call; + max_nb_ticks; } -> ( last_input_info, current_tick, @@ -190,8 +207,10 @@ struct module_reg, Some buffers, tick_state, - input_request )) - (tup7 + input_request, + last_top_level_call, + max_nb_ticks )) + (tup9 ~flatten:true (value_option ["wasm"; "input"] Wasm_pvm_sig.input_info_encoding) (value ~default:Z.zero ["wasm"; "current_tick"] Data_encoding.n) @@ -199,59 +218,84 @@ struct (scope ["modules"] Wasm_encoding.module_instances_encoding) (option durable_buffers_encoding) (scope ["wasm"] tick_state_encoding) - (scope ["input"; "consuming"] input_request_encoding)) + (scope ["input"; "consuming"] input_request_encoding) + (value + ~default:Z.zero + ["pvm"; "last_top_level_call"] + Data_encoding.n) + (value + ~default:wasm_max_tick + ["pvm"; "max_nb_ticks"] + Data_encoding.n)) let kernel_key = Durable.key_of_string_exn "/kernel/boot.wasm" let link_finished (ast : Wasm.Ast.module_) offset = offset >= Wasm.Ast.Vector.num_elements ast.it.imports - let unsafe_next_tick_state {module_reg; buffers; durable; tick_state; _} = + let is_time_for_restart {current_tick; last_top_level_call; max_nb_ticks; _} + = + let open Z in + current_tick - last_top_level_call >= max_nb_ticks - Z.one + + let unsafe_next_tick_state + ({module_reg; buffers; durable; tick_state; _} as pvm_state) = let open Lwt_syntax in + let return ?(status = Running) ?(durable = durable) state = + Lwt.return (durable, state, status) + in match tick_state with + | Stuck e -> return ~status:Failing (Stuck e) + | Eval {step_kont = Wasm.Eval.(SK_Result _); _} + when is_time_for_restart pvm_state -> + (* We have an empty set of admin instructions *) + return ~status:Restarting tick_state + | _ when is_time_for_restart pvm_state -> + (* Execution took too many ticks *) + return ~status:Failing (Stuck Too_many_ticks) | Decode {module_kont = MKStop ast_module; _} -> - Lwt.return - ( durable, - Link - { - ast_module; - externs = Wasm.Instance.Vector.empty (); - imports_offset = 0l; - } ) + return + (Link + { + ast_module; + externs = Wasm.Instance.Vector.empty (); + imports_offset = 0l; + }) | Decode m -> let* kernel = Durable.find_value_exn durable kernel_key in - let+ m = Tezos_webassembly_interpreter.Decode.module_step kernel m in - (durable, Decode m) + let* m = Tezos_webassembly_interpreter.Decode.module_step kernel m in + return (Decode m) | Link {ast_module; externs; imports_offset} when link_finished ast_module imports_offset -> let self = Wasm.Instance.Module_key wasm_main_module_name in (* The module instance is registered in [self] that contains the module registry, why we can ignore the result here. *) - Lwt.return - (durable, Init {self; ast_module; init_kont = IK_Start externs}) + return (Init {self; ast_module; init_kont = IK_Start externs}) | Link {ast_module; externs; imports_offset} -> ( - let+ {it = {module_name; item_name; _}; _} = + let* {it = {module_name; item_name; _}; _} = Wasm.Ast.Vector.get imports_offset ast_module.it.imports in match (module_name, Host_funcs.lookup_opt item_name) with | "rollup_safe_core", Some extern -> let externs, _ = Wasm.Ast.Vector.append extern externs in - ( durable, - Link - { - ast_module; - externs; - imports_offset = Int32.succ imports_offset; - } ) + return + (Link + { + ast_module; + externs; + imports_offset = Int32.succ imports_offset; + }) | "rollup_safe_core", None -> - ( durable, - Stuck (Wasm_pvm_errors.link_error `Item ~module_name ~item_name) - ) + return + ~status:Failing + (Stuck + (Wasm_pvm_errors.link_error `Item ~module_name ~item_name)) | _, _ -> - ( durable, - Stuck - (Wasm_pvm_errors.link_error `Module ~module_name ~item_name) - )) + return + ~status:Failing + (Stuck + (Wasm_pvm_errors.link_error `Module ~module_name ~item_name)) + ) | Init {self; ast_module = _; init_kont = IK_Stop _module_inst} -> ( let* module_inst = Wasm.Instance.ModuleMap.get wasm_main_module_name module_reg @@ -276,15 +320,15 @@ struct (Lazy_containers.Lazy_vector.Int32Vector.singleton admin_instr) in - Lwt.return (durable, Eval eval_config) + return ~status:Starting (Eval eval_config) | _ -> (* We require a function with the name [main] to be exported rather than any other structure. *) - Lwt.return - ( durable, - Stuck - (Invalid_state "Invalid_module: no `main` function exported") - )) + return + ~status:Failing + (Stuck + (Invalid_state "Invalid_module: no `main` function exported")) + ) | Init {self; ast_module; init_kont} -> let* init_kont = Wasm.Eval.init_step @@ -296,15 +340,27 @@ struct ast_module init_kont in - Lwt.return (durable, Init {self; ast_module; init_kont}) + return (Init {self; ast_module; init_kont}) + | Eval {step_kont = Wasm.Eval.(SK_Result _); _} -> + (* We have an empty set of admin instructions, but need to wait until we can restart *) + return tick_state + | Eval {step_kont = Wasm.Eval.(SK_Trapped msg); _} -> + return + ~status:Failing + (Stuck + (Wasm_pvm_errors.Eval_error + { + raw_exception = "trapped execution"; + explanation = Some msg.it; + })) | Eval eval_config -> + (* Continue execution. *) let store = Durable.to_storage durable in - let+ store', eval_config = + let* store', eval_config = Wasm.Eval.step ~durable:store module_reg eval_config buffers in let durable' = Durable.of_storage ~default:durable store' in - (durable', Eval eval_config) - | Stuck e -> Lwt.return (durable, Stuck e) + return ~durable:durable' (Eval eval_config) let next_tick_state pvm_state = let to_stuck exn = @@ -320,44 +376,37 @@ struct | Stuck _ -> Unknown_error error.raw_exception) | `Unknown raw_exception -> Unknown_error raw_exception in - Lwt.return (pvm_state.durable, Stuck wasm_error) + Lwt.return (pvm_state.durable, Stuck wasm_error, Failing) in Lwt.catch (fun () -> unsafe_next_tick_state pvm_state) to_stuck + let next_input_request = function + | Restarting | Failing -> Wasm_pvm_sig.Input_required + | Starting | Running -> Wasm_pvm_sig.No_input_required + + let next_last_top_level_call {current_tick; last_top_level_call; _} = + function + | Restarting -> Z.succ current_tick + | Starting | Failing | Running -> last_top_level_call + let compute_step_inner pvm_state = let open Lwt_syntax in (* Calculate the next tick state. *) - let+ durable, tick_state = next_tick_state pvm_state in - let input_request, tick_state = - match tick_state with - | Eval {step_kont = Wasm.Eval.(SK_Result _); _} -> - (* Ask for more input if the kernel has yielded (empty admin - instructions, or error). *) - (Wasm_pvm_sig.Input_required, tick_state) - | Eval {step_kont = Wasm.Eval.(SK_Trapped msg); _} -> - ( Wasm_pvm_sig.Input_required, - Stuck - (Wasm_pvm_errors.Eval_error - { - raw_exception = "trapped execution"; - explanation = Some msg.it; - }) ) - | Stuck _ -> (Wasm_pvm_sig.Input_required, tick_state) - | _ -> (Wasm_pvm_sig.No_input_required, tick_state) - in - (* Update the tick state, input-request, durable and increment the - current tick *) + let* durable, tick_state, status = next_tick_state pvm_state in + let input_request = next_input_request status in + let current_tick = Z.succ pvm_state.current_tick in + let last_top_level_call = next_last_top_level_call pvm_state status in let pvm_state = { pvm_state with tick_state; input_request; - current_tick = Z.succ pvm_state.current_tick; durable; + current_tick; + last_top_level_call; } in - - pvm_state + return pvm_state let compute_step_many ?(max_steps = 1L) tree = let open Lwt.Syntax in @@ -500,6 +549,12 @@ struct | _ -> Lwt.return_none let compute_step_many = compute_step_many + + let set_max_nb_ticks n tree = + let open Lwt_syntax in + let* pvm_state = Tree_encoding_runner.decode pvm_state_encoding tree in + let pvm_state = {pvm_state with max_nb_ticks = n} in + Tree_encoding_runner.encode pvm_state_encoding pvm_state tree end end diff --git a/src/lib_scoru_wasm/wasm_pvm_errors.ml b/src/lib_scoru_wasm/wasm_pvm_errors.ml index d03afd2719cd7fe314317aefa02661e4c4ed3416..4412f27b43f6a90cf29ece690b9d74286718eb67 100644 --- a/src/lib_scoru_wasm/wasm_pvm_errors.ml +++ b/src/lib_scoru_wasm/wasm_pvm_errors.ml @@ -34,6 +34,7 @@ type t = | Eval_error of interpreter_error | Invalid_state of string | Unknown_error of string + | Too_many_ticks let decode_state_to_string = function | Decode.Byte_vector_step -> "Byte_vector_step" @@ -139,6 +140,12 @@ let encoding = (obj1 (req "unknown_error" string)) (function Unknown_error exn -> Some exn | _ -> None) (fun exn -> Unknown_error exn); + case + (Tag 6) + ~title:"Too_many_ticks" + (constant "too_many_ticks") + (function Too_many_ticks -> Some () | _ -> None) + (fun () -> Too_many_ticks); ] let link_error kind ~module_name ~item_name = diff --git a/src/lib_scoru_wasm/wasm_pvm_errors.mli b/src/lib_scoru_wasm/wasm_pvm_errors.mli index 1d564e9d05cd338f9efd70ead73f0b54735176d4..dd2d1225a76d2076246c6cd31e2660aa6a2d71fd 100644 --- a/src/lib_scoru_wasm/wasm_pvm_errors.mli +++ b/src/lib_scoru_wasm/wasm_pvm_errors.mli @@ -50,6 +50,8 @@ type t = (** Invalid state of the PVM (waiting for input during the parsing for example). *) | Unknown_error of raw_exception (** Wraps unexpected exceptions raised by the interpreter. *) + | Too_many_ticks + (** The maximum number of ticks was reached before the end of current top level call *) (* [link_error kind ~module_name ~item_name] returns the link error for a given [module_name] and [item_name], and the kind of error (whether an unkown diff --git a/src/lib_scoru_wasm/wasm_pvm_sig.ml b/src/lib_scoru_wasm/wasm_pvm_sig.ml index 7dd9941c97904b22ce6bf8e01acd130e89320bd5..bfb17853e2d1f66c35c2b7c9c2123b221635cb12 100644 --- a/src/lib_scoru_wasm/wasm_pvm_sig.ml +++ b/src/lib_scoru_wasm/wasm_pvm_sig.ml @@ -62,6 +62,8 @@ module type Internal_for_tests = sig val is_stuck : tree -> Wasm_pvm_errors.t option Lwt.t val compute_step_many : ?max_steps:int64 -> tree -> tree Lwt.t + + val set_max_nb_ticks : Z.t -> tree -> tree Lwt.t end (** This module type defines a WASM VM API used for smart-contract rollups. *) diff --git a/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml b/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml index 9192eddef123f7e628268159e4574174babb3ba2..fb7626a1cd4cba01189c2ec34a889b5467d17d2c 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml @@ -422,6 +422,13 @@ let should_boot_computation_kernel () = let*! index = Context_binary.init "/tmp" in let context = Context_binary.empty index in let*! s = Prover.initial_state context in + (* sets a reasonable nb-of-tick limit to limit test running time *) + let*! s = + Tree.add + s + ["pvm"; "max_nb_ticks"] + (Data_encoding.Binary.to_bytes_exn Data_encoding.n (Z.of_int 50_000)) + in let*! s = Prover.install_boot_sector s boot_sector in (* installing the boot kernel *) let* s = checked_eval ~loc:__LOC__ context s in