diff --git a/src/lib_scoru_wasm/bench/exec.ml b/src/lib_scoru_wasm/bench/exec.ml index 81317742264fd67cb1681be9625da3a73ec66c6d..34196e72802c4538ffe91394e3fd8d5b543bed43 100644 --- a/src/lib_scoru_wasm/bench/exec.ml +++ b/src/lib_scoru_wasm/bench/exec.ml @@ -69,7 +69,7 @@ let run kernel k = in return res -let set_input_step = Wasm_utils.set_input_step +let set_full_input_step s = Wasm_utils.set_full_input_step [s] let read_message name = let open Tezt.Base in diff --git a/src/lib_scoru_wasm/bench/exec.mli b/src/lib_scoru_wasm/bench/exec.mli index 1fcbb840a6e4f12480631cae203578224c268dd2..1701a1392812e65772f9a8160c92fdd7e92201a2 100644 --- a/src/lib_scoru_wasm/bench/exec.mli +++ b/src/lib_scoru_wasm/bench/exec.mli @@ -47,7 +47,7 @@ val execute_on_state : (** [run path k] execute [k] on the content of the file at [path] *) val run : Lwt_io.file_name -> (string -> 'a Lwt.t) -> 'a Lwt.t -val set_input_step : string -> int -> Wasm.tree -> Wasm.tree Lwt.t +val set_full_input_step : string -> int32 -> Wasm.tree -> Wasm.tree Lwt.t (** [read_message "my_file.out"] returns the content of the file, searched in the input repository for messages*) diff --git a/src/lib_scoru_wasm/bench/scenario.ml b/src/lib_scoru_wasm/bench/scenario.ml index 5da4108557e62d7469b2806905506b92623bb34e..5b2b934ed6269455c90d5f0077ffce5c9587161b 100644 --- a/src/lib_scoru_wasm/bench/scenario.ml +++ b/src/lib_scoru_wasm/bench/scenario.ml @@ -150,7 +150,9 @@ let exec_on_message message run_state = let open Lwt_syntax in let* run_state = lift_action - (Exec.set_input_step message run_state.message_counter) + (Exec.set_full_input_step + message + (Int32.of_int run_state.message_counter)) run_state in exec_loop {run_state with message_counter = run_state.message_counter + 1} diff --git a/src/lib_scoru_wasm/constants.ml b/src/lib_scoru_wasm/constants.ml index e8a9f6bfef2a9a451f75c4570c63d7e2b88b4f20..d928c276110c8dcf7141134c309fc55872b40bb7 100644 --- a/src/lib_scoru_wasm/constants.ml +++ b/src/lib_scoru_wasm/constants.ml @@ -46,7 +46,7 @@ let wasm_max_tick = Z.of_int 11_000_000_000 (* TODO: https://gitlab.com/tezos/tezos/-/issues/3157 Find an appropriate number of reboots per inputs. *) -let maximum_reboots_per_input = Z.of_int 10 +let maximum_reboots_per_input = Z.of_int 1_000 (* Flag used in the durable storage by the kernel to ask a reboot from the PVM without consuming an input. *) @@ -61,3 +61,6 @@ let kernel_key = Durable.key_of_string_exn "/kernel/boot.wasm" let kernel_fallback_key = Durable.key_of_string_exn "/readonly/kernel/boot.wasm" let stuck_flag_key = Durable.key_of_string_exn "/readonly/kernel/env/stuck" + +let too_many_reboot_flag_key = + Durable.key_of_string_exn "/readonly/kernel/toomanyreboot" diff --git a/src/lib_scoru_wasm/durable.ml b/src/lib_scoru_wasm/durable.ml index f1f625d4c7a488395002c8e56aaa3561aad2dc52..d797a25b37f238137f502dc6e23ea26db45ccf55 100644 --- a/src/lib_scoru_wasm/durable.ml +++ b/src/lib_scoru_wasm/durable.ml @@ -129,8 +129,8 @@ let copy_tree_exn tree ?(edit_readonly = false) from_key to_key = let count_subtrees tree key = T.length tree @@ key_contents key -let delete tree key = - assert_key_writeable key ; +let delete ?(edit_readonly = false) tree key = + if not edit_readonly then assert_key_writeable key ; T.remove tree @@ key_contents key let subtree_name_at tree key index = diff --git a/src/lib_scoru_wasm/durable.mli b/src/lib_scoru_wasm/durable.mli index 0bbd607f4da630fea2ecffe431a56b84cbbc3e04..3cc60c80f0d48d9eb92ba2393fd07b52f2ebc849 100644 --- a/src/lib_scoru_wasm/durable.mli +++ b/src/lib_scoru_wasm/durable.mli @@ -101,11 +101,13 @@ val count_subtrees : t -> key -> int Lwt.t under [key]. *) val subtree_name_at : t -> key -> int -> string Lwt.t -(** [delete durable key] deletes the value at and/or subtrees of [key]. +(** [delete ?edit_readonly durable key] deletes the value at and/or + subtrees of [key]. - @raise Readonly_value + @raise Readonly_value when [edit_readonly] is not set while trying + to edit the readonly section. *) -val delete : t -> key -> t Lwt.t +val delete : ?edit_readonly:bool -> t -> key -> t Lwt.t (** [hash durable key] retrieves the tree hash of the value at the given [key]. This is not the same as the hash of the value. @@ -119,15 +121,16 @@ val hash : t -> key -> Context_hash.t option Lwt.t *) val hash_exn : t -> key -> Context_hash.t Lwt.t -(** [write_value durable key offset bytes] writes [bytes] to [key], - starting at the given [offset]. +(** [write_value ?edit_readonly durable key offset bytes] writes + [bytes] to [key], starting at the given [offset]. If no value at [key] exists, it is created. [~edit_readonly:true] allows a value to be written into a readonly location. @raise Out_of_bounds - @raise Readonly_value + @raise Readonly_value iff [edit_readonly] is not set to [true] + when attempting to write in the [readonly] section. *) val write_value_exn : t -> ?edit_readonly:bool -> key -> int64 -> string -> t Lwt.t diff --git a/src/lib_scoru_wasm/fast/vm.ml b/src/lib_scoru_wasm/fast/vm.ml index 03bd393ee92135697cb43feb516d6225467e5d02..c8dd2c40d254f5e57c52fe09129cb2fba1bf1527 100644 --- a/src/lib_scoru_wasm/fast/vm.ml +++ b/src/lib_scoru_wasm/fast/vm.ml @@ -28,73 +28,82 @@ open Wasm_pvm_state.Internal_state include (Wasm_vm : Wasm_vm_sig.S) -let compute_until_start ~max_steps pvm_state = +let compute_until_snapshot ~max_steps pvm_state = Wasm_vm.compute_step_many_until ~max_steps (fun pvm_state -> + Lwt.return + @@ match pvm_state.tick_state with - | Start -> Lwt.return false + | Snapshot -> false | _ -> Wasm_vm.should_compute pvm_state) pvm_state +let assert_decode = function Decode _ -> true | _ -> false + let compute_fast pvm_state = let open Lwt.Syntax in - let durable = pvm_state.durable in + (* Move from [Snapshot] to [Decode], to patch the durable state. *) + let* pvm_state = Wasm_vm.compute_step pvm_state in + assert (assert_decode pvm_state.tick_state) ; (* Execute! *) (* TODO: https://gitlab.com/tezos/tezos/-/issues/4123 Support performing multiple calls to [Eval.compute]. *) - let* durable = Exec.compute durable pvm_state.buffers in + let durable = pvm_state.durable in + let+ durable = Exec.compute durable pvm_state.buffers in (* Compute the new tick counter. *) - let ticks = Z.pred pvm_state.max_nb_ticks in - let current_tick = Z.add pvm_state.current_tick ticks in - (* Figure out reboot. *) - let+ status = Wasm_vm.mark_for_reboot pvm_state.reboot_counter durable in - let tick_state = - if status = Failing then Stuck Too_many_reboots else Snapshot - in - let reboot_counter = Wasm_vm.next_reboot_counter pvm_state status in + let ticks = pvm_state.max_nb_ticks in + let current_tick = Z.(add pvm_state.last_top_level_call ticks) in + (* Revert the tick state to [Snapshot]. *) + let tick_state = Snapshot in (* Assemble state *) - let pvm_state = - { - pvm_state with - durable; - current_tick; - tick_state; - reboot_counter; - last_top_level_call = current_tick; - } - in + let pvm_state = {pvm_state with durable; current_tick; tick_state} in (pvm_state, Z.to_int64 ticks) let rec compute_step_many accum_ticks ?(after_fast_exec = fun () -> ()) - ~max_steps pvm_state = + ?(stop_at_snapshot = false) ~max_steps pvm_state = let open Lwt.Syntax in assert (max_steps > 0L) ; let eligible_for_fast_exec = Z.Compare.(pvm_state.max_nb_ticks <= Z.of_int64 max_steps) in let backup pvm_state = - let+ pvm_state, ticks = Wasm_vm.compute_step_many ~max_steps pvm_state in + let+ pvm_state, ticks = + Wasm_vm.compute_step_many ~stop_at_snapshot ~max_steps pvm_state + in (pvm_state, Int64.add accum_ticks ticks) in if eligible_for_fast_exec then - let goto_start_and_retry () = - let* pvm_state, ticks = compute_until_start ~max_steps pvm_state in - let max_steps = Int64.sub max_steps ticks in - let* may_compute_more = Wasm_vm.should_compute pvm_state in - let accum_ticks = Int64.add accum_ticks ticks in - if may_compute_more && max_steps > 0L then - (compute_step_many [@tailcall]) accum_ticks ~max_steps pvm_state - else Lwt.return (pvm_state, accum_ticks) + let goto_snapshot_and_retry () = + let* pvm_state, ticks = compute_until_snapshot ~max_steps pvm_state in + match pvm_state.tick_state with + | Snapshot when not stop_at_snapshot -> + let max_steps = Int64.sub max_steps ticks in + let accum_ticks = Int64.add accum_ticks ticks in + let may_compute_more = Wasm_vm.should_compute pvm_state in + if may_compute_more && max_steps > 0L then + (compute_step_many [@tailcall]) + ~stop_at_snapshot + accum_ticks + ~max_steps + pvm_state + else Lwt.return (pvm_state, accum_ticks) + | _ -> Lwt.return (pvm_state, ticks) in let go_like_the_wind () = let+ pvm_state, ticks = compute_fast pvm_state in after_fast_exec () ; - (pvm_state, Int64.add ticks accum_ticks) + (pvm_state, Int64.(add ticks accum_ticks)) in match pvm_state.tick_state with - | Start -> Lwt.catch go_like_the_wind (fun _ -> backup pvm_state) - | _ -> goto_start_and_retry () + | Snapshot -> ( + let* reboot_mark = Wasm_vm.mark_for_reboot pvm_state in + match reboot_mark with + | `Reboot -> Lwt.catch go_like_the_wind (fun _ -> backup pvm_state) + | `Restarting | `Forcing_restart -> + (* Go to next [Collect] state *) + backup pvm_state) + | _ -> goto_snapshot_and_retry () else (* The number of ticks we're asked to do is lower than the maximum number of ticks for a top-level cycle. Fast Execution cannot be applied in this diff --git a/src/lib_scoru_wasm/pvm_input_kind.ml b/src/lib_scoru_wasm/pvm_input_kind.ml new file mode 100644 index 0000000000000000000000000000000000000000..16f6681306351c0a7e858b237eb97edfceb3245d --- /dev/null +++ b/src/lib_scoru_wasm/pvm_input_kind.ml @@ -0,0 +1,80 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 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. *) +(* *) +(*****************************************************************************) + +(* This type mimics [Sc_rollup_inbox_repr.internal_inbox_messages], without + fully deserializing the `Deposit`, and is produced by reading the first bytes + from the input: + + - `\000\000` corresponds to a deposit, + - `\000\001` a start_of_level, + - `\000\002` an end_of_level. + - Any other tag will considered as an `Other message`. *) +type internal_message_kind = Deposit | Start_of_level | End_of_level + +(* This type mimics [Sc_rollup_inbox_repr.t] and produced by reading the first + bytes from the input: + + - `\000` corresponds to an internal message, + - `\001` an external one, and its content includes the tag. + - Any other tag is considered as an `Other message`. *) +type t = Internal of internal_message_kind | External | Other + +let internal_from_raw payload = + if String.length payload < 2 then None + else + match String.get payload 1 with + | '\000' -> Some Deposit + | '\001' when String.length payload = 2 -> Some Start_of_level + | '\002' when String.length payload = 2 -> Some End_of_level + | _ -> None + +let from_raw_input payload = + if String.length payload < 1 then Other + else + match String.get payload 0 with + | '\000' -> + Option.fold + ~none:Other + ~some:(fun msg -> Internal msg) + (internal_from_raw payload) + | '\001' -> External + | _ -> Other + +module Internal_for_tests = struct + let to_binary_input input message = + match (input, message) with + | Internal Deposit, Some message -> "\000\000" ^ message + | External, Some message -> "\001" ^ message + | Internal Start_of_level, None -> "\000\001" + | Internal End_of_level, None -> "\000\002" + | Other, _ -> + Stdlib.failwith + "`Other` messages are impossible cases from the PVM perspective." + | Internal (Start_of_level | End_of_level), Some _ -> + Stdlib.failwith + "`Start_of_level` and `End_of_level` do not expect a payload" + | Internal Deposit, None -> Stdlib.failwith "`Deposit` expects a payload" + | External, None -> Stdlib.failwith "`External` expects a payload" +end diff --git a/src/lib_scoru_wasm/pvm_input_kind.mli b/src/lib_scoru_wasm/pvm_input_kind.mli new file mode 100644 index 0000000000000000000000000000000000000000..479653dab0ecd635beba2bc84c974d425f0a96de --- /dev/null +++ b/src/lib_scoru_wasm/pvm_input_kind.mli @@ -0,0 +1,55 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 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. *) +(* *) +(*****************************************************************************) + +(** [internal_message_kind] represent an internal message in a inbox. *) +type internal_message_kind = + | Deposit (* Generic internal message. *) + | Start_of_level + (** Internal message put at the beginning of each inbox's level. *) + | End_of_level (** Internal message put at the end of each inbox's level. *) + +(** A type representing messages from Layer 1 to Layer 2. Internal ones are + originated from Layer 1 smart-contracts and external ones are messages from + an external manager operation. Other messages shouldn't happen and treated + as errors by the PVM, it represents unexpected tags. *) +type t = Internal of internal_message_kind | External | Other + +(** [from_raw_input input] takes a message produced by the L1 protocol and + returns its kind. *) +val from_raw_input : string -> t + +module Internal_for_tests : sig + (** [to_binary_input kind input] returns the serialized representation of an + [input] according to its [kind]. Internal message payloads and external + message payloads are prefixed by their tag, and `Other` messages result in + an exception. These messages are meant to be used in tests only, and does + not give any guarantee on their validity according to the protocol's + representation. + + @raise Failure on `Other` messages, and mismatches between kind and + presence or absence of the input. +*) + val to_binary_input : t -> string option -> string +end diff --git a/src/lib_scoru_wasm/test/helpers/wasm_utils.ml b/src/lib_scoru_wasm/test/helpers/wasm_utils.ml index 4b8b60d99c2854f8748a7a950c186a0f1df8c79e..63bc7897dd2b4f0109f565dac2439b7d304fea84 100644 --- a/src/lib_scoru_wasm/test/helpers/wasm_utils.ml +++ b/src/lib_scoru_wasm/test/helpers/wasm_utils.ml @@ -66,6 +66,26 @@ let eval_until_stuck ?(max_steps = 20000L) tree = in go max_steps tree +(* This function relies on the invariant that `compute_step_many` will always + stop at a Snapshot or an input request, and never start another + `kernel_next`. *) +let rec eval_to_snapshot ?(max_steps = Int64.max_int) tree = + let open Lwt_syntax in + let eval tree = + let* tree, _ = + Wasm.compute_step_many ~stop_at_snapshot:true ~max_steps tree + in + let* state = Wasm.Internal_for_tests.get_tick_state tree in + match state with + | Snapshot -> return tree + | _ -> eval_to_snapshot ~max_steps tree + in + let* info = Wasm.get_info tree in + match info.input_request with + | No_input_required -> eval tree + | Input_required | Reveal_required _ -> + Stdlib.failwith "Cannot reach snapshot point" + let rec eval_until_input_requested ?after_fast_exec ?(fast_exec = false) ?(max_steps = Int64.max_int) tree = let open Lwt_syntax in @@ -78,11 +98,66 @@ let rec eval_until_input_requested ?after_fast_exec ?(fast_exec = false) match info.input_request with | No_input_required -> let* tree, _ = run ~max_steps tree in - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4175 - We don't pass [~max_steps] here because some tests are buggy. *) - eval_until_input_requested ?after_fast_exec ~fast_exec tree + eval_until_input_requested ~max_steps tree | Input_required | Reveal_required _ -> return tree +let input_info level message_counter = + Wasm_pvm_state. + { + inbox_level = + Option.value_f ~default:(fun () -> assert false) + @@ Tezos_base.Bounded.Non_negative_int32.of_value level; + message_counter; + } + +let new_message_counter () = + let c = ref Z.zero in + fun () -> + c := Z.succ !c ; + Z.pred !c + +let set_sol_input level tree = + let sol_input = + Pvm_input_kind.( + Internal_for_tests.to_binary_input (Internal Start_of_level) None) + in + Wasm.set_input_step (input_info level Z.zero) sol_input tree + +let set_internal_message level counter message tree = + let encoded_message = + Pvm_input_kind.( + Internal_for_tests.to_binary_input (Internal Deposit) (Some message)) + in + Wasm.set_input_step (input_info level counter) encoded_message tree + +let set_eol_input level counter tree = + let sol_input = + Pvm_input_kind.( + Internal_for_tests.to_binary_input (Internal End_of_level) None) + in + Wasm.set_input_step (input_info level counter) sol_input tree + +let set_inputs_step messages level tree = + let open Lwt_syntax in + let next_message_counter = new_message_counter () in + let (_ : Z.t) = next_message_counter () in + let* tree = set_sol_input level tree in + let* tree = + List.fold_left_s + (fun tree message -> + set_internal_message level (next_message_counter ()) message tree) + tree + messages + in + set_eol_input level (next_message_counter ()) tree + +let set_full_input_step messages level tree = + let open Lwt_syntax in + let* tree = set_inputs_step messages level tree in + eval_to_snapshot ~max_steps:Int64.max_int tree + +let set_empty_inbox_step level tree = set_full_input_step [] level tree + let rec eval_until_init tree = let open Lwt_syntax in let* state_after_first_message = @@ -109,13 +184,13 @@ let set_input_step message message_counter tree = let pp_state fmt state = let pp_s s = Format.fprintf fmt "%s" s in match state with - | Wasm_pvm_state.Internal_state.Start -> pp_s "Start" + | Wasm_pvm_state.Internal_state.Snapshot -> pp_s "Start" | Decode _ -> pp_s "Decode" | Eval _ -> pp_s "Eval" | Stuck e -> Format.fprintf fmt "Stuck (%a)" Test_wasm_pvm_encodings.pp_error_state e | Init _ -> pp_s "Init" - | Snapshot -> pp_s "Snapshot" + | Collect -> pp_s "Collect" | Link _ -> pp_s "Link" | Padding -> pp_s "Padding" diff --git a/src/lib_scoru_wasm/test/test_fast.ml b/src/lib_scoru_wasm/test/test_fast.ml index e126bad2657e770d83a123acd0a9cb5f0ee97e68..efdd88bc69171b4f03ab603dd8a841f1d4bc6ad3 100644 --- a/src/lib_scoru_wasm/test/test_fast.ml +++ b/src/lib_scoru_wasm/test/test_fast.ml @@ -27,18 +27,18 @@ open Tztest let apply_fast counter tree = let open Lwt.Syntax in - let run_counter = ref 0 in + let run_counter = ref 0l in let+ tree = Wasm_utils.eval_until_input_requested - ~after_fast_exec:(fun () -> run_counter := succ !run_counter) + ~after_fast_exec:(fun () -> run_counter := Int32.succ !run_counter) ~fast_exec:true ~max_steps:Int64.max_int tree in - if counter > 0 then + if counter > 0l then (* Assert that the FE actual ran. We must consider that before the first message is inserted, FE is unlikely to run. *) - if !run_counter <> 1 then + if !run_counter <> 1l then Stdlib.failwith "Fast Execution was expected to run!" ; tree @@ -59,10 +59,10 @@ let test_against_both ~from_binary ~kernel ~messages = let* info = Wasm_utils.Wasm.get_info tree in assert (info.input_request = Input_required) ; - let+ tree = Wasm_utils.set_input_step message counter tree in + let+ tree = Wasm_utils.set_full_input_step [message] counter tree in let hash2 = Tezos_context_memory.Context_binary.Tree.hash tree in - (tree, succ counter, hash1 :: hash2 :: hashes) + (tree, Int32.succ counter, hash1 :: hash2 :: hashes) in let* initial_tree = @@ -71,7 +71,7 @@ let test_against_both ~from_binary ~kernel ~messages = let run_with apply = let* tree, counter, hashes = - List.fold_left_s (make_folder apply) (initial_tree, 0, []) messages + List.fold_left_s (make_folder apply) (initial_tree, 0l, []) messages in let+ tree = apply counter tree in diff --git a/src/lib_scoru_wasm/test/test_fixed_nb_ticks.ml b/src/lib_scoru_wasm/test/test_fixed_nb_ticks.ml index 9b325b44e04c2ce6087f5b00c308cb0fe561449f..e7a29375948b82f73c4b5bdfbe95a243bc9222d6 100644 --- a/src/lib_scoru_wasm/test/test_fixed_nb_ticks.ml +++ b/src/lib_scoru_wasm/test/test_fixed_nb_ticks.ml @@ -67,9 +67,8 @@ let test_looping_kernel () = (* This module loops indefinitely. *) let*! loop_module_tree = initial_tree ~max_tick:max_nb_ticks loop_module in - let*! tree_with_dummy_input = - set_input_step "dummy_input" 0 loop_module_tree - in + let*! loop_module_tree = eval_until_input_requested loop_module_tree in + let*! tree_with_dummy_input = set_empty_inbox_step 0l loop_module_tree in let* stuck, _ = eval_until_stuck tree_with_dummy_input in match stuck with | Too_many_ticks -> return_unit @@ -86,34 +85,32 @@ let test_noop_kernel () = let*! tree_snapshotted = eval_until_input_requested noop_module_tree in (* Adds one input tick, part of the maximum number of ticks per toplevel call. *) - let*! tree_with_dummy_input = - set_input_step "dummy_input" 0 tree_snapshotted - in - let*! tree = eval_until_input_requested tree_with_dummy_input in + let*! tree_with_dummy_input = set_empty_inbox_step 0l tree_snapshotted in + let*! tree = eval_to_snapshot tree_with_dummy_input 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))) + (* Twice as much as max ticks, one to load the inbox, the other to execute. *) + return (assert (Z.(info.current_tick = of_int64 Int64.(mul 2L max_nb_ticks)))) let test_stuck_in_decode_kernel () = let open Lwt_result_syntax in - (* For now the PVM will always do at least 2 ticks per toplevel calls: one for - the input tick, and another for the tick leading to the `Too_many_ticks` - state. *) - let max_nb_ticks = 2L in + (* The PVM needs at least [4] ticks to load the smallest inbox possible: + - 1 for Snapshot --> Collect + - 1 for SOL + - 1 for EOL + - 1 for Collect -> Padding *) + let max_nb_ticks = 4L in (* This module does a noop. *) let*! noop_module_tree = initial_tree ~max_tick:max_nb_ticks noop_module in - (* Adds one input tick, part of the maximum number of ticks per toplevel - call. *) - let*! tree_with_dummy_input = - set_input_step "dummy_input" 0 noop_module_tree - in + let*! noop_module_tree = eval_until_input_requested noop_module_tree in + (* Collect an inbox. *) + let*! tree_with_dummy_input = set_empty_inbox_step 0l noop_module_tree in (* Eval one tick *) let* stuck, tree = eval_until_stuck tree_with_dummy_input 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))) + (* Twice as much as max ticks, one to load the inbox, the other to execute. *) + return (assert (Z.(info.current_tick = of_int64 Int64.(mul 2L max_nb_ticks)))) let test_stuck_in_init_kernel () = let open Lwt_result_syntax in @@ -121,11 +118,10 @@ let test_stuck_in_init_kernel () = (* This module does a noop. *) let*! noop_module_tree = initial_tree ~max_tick:max_nb_ticks noop_module in + let*! noop_module_tree = eval_until_input_requested noop_module_tree in (* Adds one input tick, part of the maximum number of ticks per toplevel call. *) - let*! tree_with_dummy_input = - set_input_step "dummy_input" 0 noop_module_tree - in + let*! tree_with_dummy_input = set_empty_inbox_step 0l noop_module_tree in (* go to first Init step *) let*! tree = eval_until_init tree_with_dummy_input in let*! stuck = Wasm.Internal_for_tests.is_stuck tree in @@ -133,7 +129,11 @@ let test_stuck_in_init_kernel () = (* set maximum to next tick and eval one more time *) let*! info = Wasm.get_info tree in - let new_max_nb_ticks = Z.succ info.current_tick in + let previous_max_nb_ticks = Z.of_int64 max_nb_ticks in + let new_max_nb_ticks = + (* Remove the input step ticks *) + Z.(succ (sub info.current_tick previous_max_nb_ticks)) + in let*! tree = Wasm.Internal_for_tests.set_max_nb_ticks new_max_nb_ticks tree in let*! tree = Wasm.compute_step tree in @@ -141,7 +141,8 @@ let test_stuck_in_init_kernel () = let*! info = Wasm.get_info tree in let*! stuck = Wasm.Internal_for_tests.is_stuck tree in assert (stuck = Some Too_many_ticks) ; - return (assert (info.current_tick = new_max_nb_ticks)) + return + (assert (info.current_tick = Z.add previous_max_nb_ticks new_max_nb_ticks)) let tests = [ diff --git a/src/lib_scoru_wasm/test/test_get_set.ml b/src/lib_scoru_wasm/test/test_get_set.ml index f6c4b4883f2e542a148d265db7c5d12d2ee121af..67c5c60bc71fd8288d25ce4b09c8de22896c8726 100644 --- a/src/lib_scoru_wasm/test/test_get_set.ml +++ b/src/lib_scoru_wasm/test/test_get_set.ml @@ -123,16 +123,7 @@ let test_get_info () = let last_input_read = Some (make_inbox_info ~inbox_level ~message_counter) in - { - current_tick = Z.zero; - last_input_read; - input_request = - Input_required - (* While it shouldn't be Input_required after an input, `add_input_info` - doesn't use the PVM interface but encodes it directly into the tree. - The tree is in `Input_required` state since it is in snapshot state, - waiting for the next_input. *); - } + {current_tick = Z.zero; last_input_read; input_request = No_input_required} in let* actual_info = Wasm.get_info tree in assert (actual_info.last_input_read = None) ; @@ -149,7 +140,7 @@ let encode_tick_state tree = let* tree = Tree_encoding_runner.encode (Tezos_tree_encoding.value ["wasm"; "tag"] Data_encoding.string) - "snapshot" + "collect" tree in (* Encode the value. *) @@ -171,7 +162,7 @@ let test_set_input () = let* tree = Wasm.set_input_step {inbox_level = zero; message_counter = Z.of_int 1} - "hello" + "\000\000hello" tree in let* buffers = @@ -189,12 +180,12 @@ let test_set_input () = let last_input_read = Some (make_inbox_info ~inbox_level:5 ~message_counter:10) in - {current_tick = Z.one; last_input_read; input_request = No_input_required} + {current_tick = Z.one; last_input_read; input_request = Input_required} in let* actual_info = Wasm.get_info tree in assert (actual_info = expected_info) ; assert (current_tick = Z.one) ; - assert (Bytes.to_string result_message.payload = "hello") ; + assert (Bytes.to_string result_message.payload = "\000\000hello") ; Lwt_result_syntax.return_unit (** Given a [config] whose output has a given payload at position (0,0), if we diff --git a/src/lib_scoru_wasm/test/test_hash_consistency.ml b/src/lib_scoru_wasm/test/test_hash_consistency.ml index 91a3574d53c65c40d7406329bf0a2b32e3ae75f5..594269d1b1214925d5e08fa9fbf7d529f79c64f8 100644 --- a/src/lib_scoru_wasm/test/test_hash_consistency.ml +++ b/src/lib_scoru_wasm/test/test_hash_consistency.ml @@ -39,9 +39,7 @@ let test_execution_correspondance skip count () = let open Lwt_result_syntax in let*! tree = initial_tree ~from_binary:true ~max_tick:40_000L kernel in let*! tree_snapshotted = eval_until_input_requested tree in - let*! tree_with_dummy_input = - set_input_step "dummy_input" 0 tree_snapshotted - in + let*! tree_with_dummy_input = set_empty_inbox_step 0l tree_snapshotted in let*! tree = if skip = 0L then Lwt.return tree_with_dummy_input else diff --git a/src/lib_scoru_wasm/test/test_init.ml b/src/lib_scoru_wasm/test/test_init.ml index a096a4fd12c59ee05a175cab69106aad66ecd19c..b6b965187e7c37be03b224f654b367321dd12781 100644 --- a/src/lib_scoru_wasm/test/test_init.ml +++ b/src/lib_scoru_wasm/test/test_init.ml @@ -33,7 +33,8 @@ let test_memory0_export () = let*! bad_module_tree = initial_tree {| (module (memory 1)) |} in - let*! bad_module_tree = set_input_step "dummy_input" 0 bad_module_tree in + let*! bad_module_tree = eval_until_input_requested bad_module_tree in + let*! bad_module_tree = set_empty_inbox_step 0l bad_module_tree in let* stuck, _ = eval_until_stuck bad_module_tree in assert ( check_error @@ -55,7 +56,8 @@ let test_memory0_export () = ) |} in - let*! good_module_tree = set_input_step "dummy_input" 0 good_module_tree in + let*! good_module_tree = eval_until_input_requested good_module_tree in + let*! good_module_tree = set_empty_inbox_step 0l good_module_tree in let*! tree = eval_until_input_requested good_module_tree in let*! stuck_flag = has_stuck_flag tree in return (assert stuck_flag) @@ -85,7 +87,8 @@ let test_module_name_size () = (build size) in let*! bad_module_tree = initial_tree (build_module 513) in - let*! bad_module_tree = set_input_step "dummy_input" 0 bad_module_tree in + let*! bad_module_tree = eval_until_input_requested bad_module_tree in + let*! bad_module_tree = set_empty_inbox_step 0l bad_module_tree in let* stuck, _ = eval_until_stuck bad_module_tree in assert ( check_error @@ -93,7 +96,8 @@ let test_module_name_size () = ~expected_reason:"Names cannot exceed 512 bytes" stuck) ; let*! good_module_tree = initial_tree (build_module 512) in - let*! good_module_tree = set_input_step "dummy_input" 0 good_module_tree in + let*! good_module_tree = eval_until_input_requested good_module_tree in + let*! good_module_tree = set_empty_inbox_step 0l good_module_tree in let*! tree = eval_until_input_requested good_module_tree in let*! stuck_flag = has_stuck_flag tree in return (assert stuck_flag) @@ -122,7 +126,8 @@ let test_imports () = let*! bad_module_tree = initial_tree (build_module bad_module_name bad_item_name) in - let*! bad_module_tree = set_input_step "dummy_input" 0 bad_module_tree in + let*! bad_module_tree = eval_until_input_requested bad_module_tree in + let*! bad_module_tree = set_empty_inbox_step 0l bad_module_tree in let* stuck, _ = eval_until_stuck bad_module_tree in let* () = let expected_error = @@ -138,9 +143,8 @@ let test_imports () = let*! bad_host_func_tree = initial_tree (build_module good_module_name bad_item_name) in - let*! bad_host_func_tree = - set_input_step "dummy_input" 0 bad_host_func_tree - in + let*! bad_host_func_tree = eval_until_input_requested bad_host_func_tree in + let*! bad_host_func_tree = set_empty_inbox_step 0l bad_host_func_tree in let* stuck, _ = eval_until_stuck bad_host_func_tree in let* () = let expected_error = @@ -156,7 +160,8 @@ let test_imports () = let*! good_module_tree = initial_tree (build_module good_module_name good_item_name) in - let*! good_module_tree = set_input_step "dummy_input" 0 good_module_tree in + let*! good_module_tree = eval_until_input_requested good_module_tree in + let*! good_module_tree = set_empty_inbox_step 0l good_module_tree in let*! tree = eval_until_input_requested good_module_tree in let*! stuck_flag = has_stuck_flag tree in return (assert stuck_flag) @@ -182,7 +187,8 @@ let test_host_func_start_restriction () = ) |} in - let*! state = set_input_step "dummy_input" 0 state in + let*! state = eval_until_input_requested state in + let*! state = set_empty_inbox_step 0l state in let+ stuck, _ = eval_until_stuck state in assert ( check_error diff --git a/src/lib_scoru_wasm/test/test_reveal.ml b/src/lib_scoru_wasm/test/test_reveal.ml index c3c186c0b634da8824e35f20369fb4108249141c..3155543cc67f9a0b42898528d667a91d56d8f959 100644 --- a/src/lib_scoru_wasm/test/test_reveal.ml +++ b/src/lib_scoru_wasm/test/test_reveal.ml @@ -98,9 +98,7 @@ let test_reveal_preimage_gen preimage max_bytes = let modl = reveal_preimage_module hash_addr preimage_addr max_bytes in let*! state = initial_tree modl in let*! state_snapshotted = eval_until_input_requested state in - let*! state_with_dummy_input = - set_input_step "dummy_input" 0 state_snapshotted - in + let*! state_with_dummy_input = set_empty_inbox_step 0l state_snapshotted in (* Let’s go *) let*! state = eval_until_input_requested state_with_dummy_input in let*! info = Wasm.get_info state in @@ -171,9 +169,7 @@ let test_reveal_metadata () = let modl = reveal_metadata_module metadata_addr in let*! state = initial_tree modl in let*! state_snapshotted = eval_until_input_requested state in - let*! state_with_dummy_input = - set_input_step "dummy_input" 0 state_snapshotted - in + let*! state_with_dummy_input = set_empty_inbox_step 0l state_snapshotted in (* Let’s go *) let*! state = eval_until_input_requested state_with_dummy_input in let*! info = Wasm.get_info state in diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index 1e5f63f78cec8cf7ca63aab58bdef463d2ad45ea..be0e909097d5b50a91316a9e414df6232a405446 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -44,7 +44,7 @@ let should_boot_unreachable_kernel ~max_steps kernel = “Input_requested” mode. *) let* tree = eval_until_input_requested ~max_steps tree in (* Feeding it with one input *) - let* tree = set_input_step "test" 0 tree in + let* tree = set_empty_inbox_step 0l tree in (* running until waiting for input *) let* tree = eval_until_input_requested ~max_steps tree in let* info_after_first_message = Wasm.get_info tree in @@ -54,7 +54,7 @@ let should_boot_unreachable_kernel ~max_steps kernel = let* stuck_flag = has_stuck_flag tree in assert stuck_flag ; (* Feeding it with one input *) - let* tree = set_input_step "test" 1 tree in + let* tree = set_empty_inbox_step 1l tree in (* running until waiting for input *) let* tree = eval_until_input_requested tree in let* info_after_second_message = Wasm.get_info tree in @@ -99,7 +99,7 @@ let should_run_debug_kernel () = “Input_requested” mode. *) let* tree = eval_until_input_requested tree in (* Feeding it with one input *) - let* tree = set_input_step "test" 0 tree in + let* tree = set_empty_inbox_step 0l tree in (* running until waiting for input *) let* tree = eval_until_input_requested tree in let* stuck_flag = has_stuck_flag tree in @@ -181,13 +181,13 @@ let should_run_store_has_kernel () = let* stuck_flag = has_stuck_flag tree in assert (not stuck_flag) ; (* We first evaluate the kernel normally, and it shouldn't fail. *) - let* tree = set_input_step "test" 0 tree in + let* tree = set_empty_inbox_step 0l tree in let* tree = eval_until_input_requested tree in (* The kernel is not expected to fail, the PVM should not have stuck state on. *) let* stuck_flag = has_stuck_flag tree in assert (not stuck_flag) ; - let* tree = set_input_step "test" 1 tree in + let* tree = set_empty_inbox_step 1l tree in (* We now delete the path ["hello"; "universe"] - this will cause gthe kernel assertion on this path to fail, and the PVM should become stuck on the third assert in the kernel. *) @@ -239,7 +239,7 @@ let should_run_store_list_size_kernel () = “Input_requested” mode. *) let* tree = eval_until_input_requested tree in (* Feeding it with one input *) - let* tree = set_input_step "test" 0 tree in + let* tree = set_empty_inbox_step 0l tree in (* running until waiting for input *) let* tree = eval_until_input_requested tree in let* state_after_first_message = @@ -249,7 +249,7 @@ let should_run_store_list_size_kernel () = assert (not @@ is_stuck state_after_first_message) ; (* We now add another value - this will cause the kernel assertion on this path to fail, as there are now four subtrees. *) - let* tree = set_input_step "test" 1 tree in + let* tree = set_empty_inbox_step 1l tree in let* tree = add_value tree ["one"; "four"] in let* tree = eval_until_input_requested tree in (* The kernel is now expected to fail, the PVM should have the stuck tag. *) @@ -311,7 +311,7 @@ let should_run_store_delete_kernel () = (* Eval until input requested: the initial step will be "Snapshot", which is an input step. It needs an input to be forced to initialize. *) let* tree = eval_until_input_requested tree in - let* tree = set_input_step "moving forward" 0 tree in + let* tree = set_empty_inbox_step 0l tree in let* tree = eval_until_input_requested tree in (* The kernel is not expected to fail, the PVM should not have stuck flag. *) let* stuck_flag = has_stuck_flag tree in @@ -381,7 +381,7 @@ let should_run_store_move_kernel () = in assert (not @@ is_stuck state_before_first_message) ; (* consume a message and run kernel_next until we need next message *) - let* tree = set_input_step "test" 0 tree in + let* tree = set_empty_inbox_step 0l tree in let* tree = eval_until_input_requested tree in let* state_after_first_message = Wasm.Internal_for_tests.get_tick_state tree @@ -444,7 +444,7 @@ let should_run_store_copy_kernel () = in assert (not @@ is_stuck state_before_first_message) ; (* consume a message and run kernel_next until we need next message *) - let* tree = set_input_step "test" 0 tree in + let* tree = set_empty_inbox_step 0l tree in let* tree = eval_until_input_requested tree in let* state_after_first_message = Wasm.Internal_for_tests.get_tick_state tree @@ -538,7 +538,7 @@ let test_modify_read_only_storage_kernel () = let* stuck_flag = has_stuck_flag tree in assert (not stuck_flag) ; (* We first evaluate the kernel normally, and it shouldn't fail. *) - let* tree = set_input_step "test" 0 tree in + let* tree = set_empty_inbox_step 0l tree in let* tree = eval_until_input_requested tree in (* The kernel is not expected to fail, the PVM should not have stuck state on. *) @@ -562,7 +562,7 @@ let build_snapshot_wasm_state_from_set_input [ case "snapshot" - (value [] Data_encoding.unit) + (return ()) (function Snapshot -> Some () | _ -> None) (fun () -> Snapshot); ]) @@ -574,15 +574,16 @@ let build_snapshot_wasm_state_from_set_input Snapshot tree in - (* Since we start directly after at an input_step, we need to offset the tick - at the next max tick + the snapshot tick. *) - let* current_tick = + (* Since we start directly after reading an inbox. *) + let* previous_top_level_call = Test_encodings_util.Tree_encoding_runner.decode (Tezos_tree_encoding.value ["pvm"; "last_top_level_call"] Data_encoding.n) tree in - let snapshot_tick = Z.(max_tick + current_tick) in + let new_last_top_level_call = Z.(max_tick + previous_top_level_call) in + + let snapshot_tick = Z.(max_tick + new_last_top_level_call) in let* tree = Test_encodings_util.Tree_encoding_runner.encode @@ -590,11 +591,11 @@ let build_snapshot_wasm_state_from_set_input snapshot_tick tree in - let* tree = Wasm.Internal_for_tests.reset_reboot_counter tree in + let* tree = Wasm.Internal_for_tests.decr_reboot_counter tree in let* tree = Test_encodings_util.Tree_encoding_runner.encode (Tezos_tree_encoding.value ["pvm"; "last_top_level_call"] Data_encoding.n) - snapshot_tick + new_last_top_level_call tree in (* The kernel will have been set as the fallback kernel. *) @@ -610,6 +611,9 @@ let build_snapshot_wasm_state_from_set_input Constants.kernel_key Constants.kernel_fallback_key in + (* And the PVM will have cleared the request for a reboot set by the + collect phase *) + let* durable = Durable.delete durable Constants.reboot_flag_key in Test_encodings_util.Tree_encoding_runner.encode (Tezos_tree_encoding.scope ["durable"] Durable.encoding) durable @@ -634,7 +638,7 @@ let test_snapshotable_state () = let*! state = Wasm.Internal_for_tests.get_tick_state tree in let* () = match state with - | Snapshot -> return_unit + | Collect -> return_unit | _ -> failwith "Unexpected state at input requested: %a" @@ -651,13 +655,13 @@ let test_snapshotable_state () = in assert (not (wasm_tree_exists || wasm_value_exists)) ; (* Set a new input should go back to decoding *) - let*! tree = set_input_step "test" 1 tree in + let*! tree = set_empty_inbox_step 1l tree in let*! state = Wasm.Internal_for_tests.get_tick_state tree in match state with - | Start -> return_unit + | Snapshot -> return_unit | _ -> failwith - "Unexpected state after set_input_step: %a" + "Unexpected state after set_empty_inbox_step: %a" Wasm_utils.pp_state state @@ -676,9 +680,9 @@ let test_rebuild_snapshotable_state () = in let*! tree = initial_tree ~from_binary:false module_ in let*! tree = eval_until_input_requested tree in - let*! tree = set_input_step "test" 0 tree in + let*! tree = set_empty_inbox_step 0l tree in (* First evaluate until the snapshotable state. *) - let*! tree_after_eval = eval_until_input_requested tree in + let*! tree_after_eval = eval_to_snapshot tree in (* From out initial tree, let's rebuild the expected snapshotable state as the Fast Node would do. This works because the kernel doesn't change anything. *) let*! rebuilded_tree = build_snapshot_wasm_state_from_set_input tree in @@ -705,8 +709,12 @@ let test_rebuild_snapshotable_state () = (* To be sure, let's try to evaluate a new input from these two, either by the regular tree or the snapshoted one. *) - let*! input_step_from_eval = set_input_step "test" 1 tree_after_eval in - let*! input_step_from_snapshot = set_input_step "test" 1 rebuilded_tree in + (* First, we reach the collect state *) + let*! tree_after_eval = Wasm.compute_step tree_after_eval in + let*! rebuilded_tree = Wasm.compute_step rebuilded_tree in + (* Then we eval *) + let*! input_step_from_eval = set_empty_inbox_step 1l tree_after_eval in + let*! input_step_from_snapshot = set_empty_inbox_step 1l rebuilded_tree in (* Their hash should still be the same, but the first test should have caught that. *) @@ -748,9 +756,7 @@ let test_unkown_host_function_truncated () = let*! tree = initial_tree ~from_binary:false module_ in (* The tree should be in snapshot state by default, hence in input step. *) let*! tree_snapshotted = eval_until_input_requested tree in - let*! tree_with_dummy_input = - set_input_step "dummy_input" 0 tree_snapshotted - in + let*! tree_with_dummy_input = set_empty_inbox_step 0l tree_snapshotted in let*! tree_stuck = eval_until_input_requested tree_with_dummy_input in let*! state = Wasm.Internal_for_tests.get_tick_state tree_stuck in (* The message as originally outputed before being @@ -781,7 +787,8 @@ let test_bulk_noops () = |} in let* base_tree = initial_tree ~max_tick:500L module_ in - let* base_tree = set_input_step "dummy_input" 0 base_tree in + let* base_tree = eval_until_input_requested base_tree in + let* base_tree = set_empty_inbox_step 0l base_tree in let rec goto_snapshot ticks tree_slow = let* tree_fast, _ = Wasm.compute_step_many ~max_steps:ticks base_tree in @@ -797,19 +804,9 @@ let test_bulk_noops () = else goto_snapshot (Int64.succ ticks) tree_slow in - let* ticks, snapshot = goto_snapshot 1L base_tree in + let* _ticks, snapshot = goto_snapshot 1L base_tree in let* snapshot_info = Wasm_utils.Wasm.get_info snapshot in - assert (snapshot_info.input_request = Input_required) ; - - (* Try to advance past the snapshot point. *) - let* tree_fast, _ = - Wasm.compute_step_many ~max_steps:(Int64.mul ticks 2L) base_tree - in - - (* Because the Snapshot state is an input state, the [compute_step_many] - invocation must not be able to zoom past it! *) - assert ( - Context_hash.(Test_encodings_util.Tree.(hash tree_fast = hash snapshot))) ; + assert (snapshot_info.input_request = No_input_required) ; Lwt_result_syntax.return_unit @@ -884,7 +881,7 @@ let test_durable_store_io () = assert (Option.is_none value) ; (* Set input and eval again. *) - let*! tree = set_input_step "dummy_input" 0 tree in + let*! tree = set_empty_inbox_step 0l tree in let*! tree = eval_until_input_requested tree in let*! stuck_flag = has_stuck_flag tree in assert (not stuck_flag) ; @@ -992,7 +989,7 @@ let test_reveal_upgrade_kernel_ok () = let*! () = assert_kernel tree @@ Some modul in let*! () = assert_fallback_kernel tree None in (* Run the kernel, it should request a preimage reveal *) - let*! tree = set_input_step "test" 0 tree in + let*! tree = set_empty_inbox_step 0l tree in let*! tree = eval_until_input_requested tree in (* At this point, the kernel should be installed, including as fallback *) let*! () = assert_kernel tree @@ Some modul in @@ -1045,7 +1042,7 @@ let test_reveal_upgrade_kernel_ok () = let*! () = assert_kernel tree @@ Some preimage in let*! () = assert_fallback_kernel tree @@ Some modul in (* run with new input, this should be the new kernel *) - let*! tree = set_input_step "test" 2 tree in + let*! tree = set_empty_inbox_step 2l tree in let*! tree = eval_until_input_requested tree in let*! state_after_second_message = Wasm.Internal_for_tests.get_tick_state tree @@ -1071,7 +1068,7 @@ let test_reveal_upgrade_kernel_fallsback_on_error ~binary ~error invalid_kernel let*! () = assert_kernel tree @@ Some modul in let*! () = assert_fallback_kernel tree None in (* Run the kernel, it should request a preimage reveal *) - let*! tree = set_input_step "test" 0 tree in + let*! tree = set_empty_inbox_step 0l tree in let*! tree = eval_until_input_requested tree in (* At this point, the kernel should be installed, including as fallback *) let*! () = assert_kernel tree @@ Some modul in @@ -1115,7 +1112,7 @@ let test_reveal_upgrade_kernel_fallsback_on_error ~binary ~error invalid_kernel let*! () = assert_kernel tree @@ Some preimage in let*! () = assert_fallback_kernel tree @@ Some modul in (* run with new input, this should be the new kernel *) - let*! tree = set_input_step "test" 2 tree in + let*! tree = set_empty_inbox_step 2l tree in let*! tree = eval_until_input_requested tree in let*! state_after_second_message = Wasm.Internal_for_tests.get_tick_state tree @@ -1289,19 +1286,21 @@ let test_kernel_reboot_gen ~reboots ~expected_reboots ~pvm_max_reboots = reboot_module in let*! tree = eval_until_input_requested tree in - let*! tree = set_input_step "dummy_input" 0 tree in + let*! tree = set_empty_inbox_step 0l tree in let*! tree = eval_until_input_requested tree in - let*! state = Wasm.Internal_for_tests.get_tick_state tree in - + let*! durable = wrap_as_durable_storage tree in + let durable = Durable.of_storage_exn durable in + let*! too_many_reboot = + Durable.find_value durable Constants.too_many_reboot_flag_key + in + let too_many_reboot = Option.is_some too_many_reboot in (* If the expected number of reboots from the PVM is lower than the maximum asked by the kernel itself, this should lead to a stuck state with `Too_many_reboots`. *) let*! stuck_flag = has_stuck_flag tree in if reboots <= pvm_max_reboots then assert (not stuck_flag) - else assert (is_stuck ~step:`Too_many_reboots state) ; + else assert too_many_reboot ; - let*! durable = wrap_as_durable_storage tree in - let durable = Durable.of_storage_exn durable in let*! value = Durable.find_value durable (Durable.key_of_string_exn "/reboot/counter") in @@ -1326,6 +1325,102 @@ let test_kernel_reboot_failing () = then fail after 10. *) test_kernel_reboot_gen ~reboots:15l ~expected_reboots:10l ~pvm_max_reboots:10l +(* Set a certain number `n` of dummy inputs and check the scheduling is + consistent: + - `Collect` as initial state + - `Collect` after Start_of_level + - `Collect` after `n` internal messages + - `Eval` after End_of_level +*) +let test_set_inputs number_of_inputs level tree = + let open Lwt_syntax in + let next_message_counter = new_message_counter () in + + (* [set_input_and_check] takes a function that from a counter returns a tree, + and the expected kind of scheduling status after `set_input`. *) + let set_input_and_check set_input = + let counter = next_message_counter () in + let+ tree = set_input counter in + tree + in + + (* First set `Start_of_level` and check the result will be `Collect`: still + waiting for inputs. *) + let* tree_with_sol = + set_input_and_check (fun _ -> set_sol_input level tree) + in + + (* Then, adds `number_of_inputs` messages to the PVM, and check the scheduling + status after each message. *) + let inputs = + List.init ~when_negative_length:[] number_of_inputs string_of_int + |> Stdlib.Result.get_ok + in + let* tree_with_inputs = + List.fold_left_s + (fun tree input -> + set_input_and_check (fun counter -> + set_internal_message level counter input tree)) + tree_with_sol + inputs + in + + (* Finally, set `End_of_level`. The state should be padding. *) + let* tree = + set_input_and_check (fun counter -> + set_eol_input level counter tree_with_inputs) + in + + let+ state = Wasm.Internal_for_tests.get_tick_state tree in + assert (state = Padding) ; + tree + +let test_scheduling_multiple_inboxes input_numbers = + let open Lwt_result_syntax in + let module_ = + {| + (module + (memory 0) + (export "mem" (memory 0)) + (func (export "kernel_next") + (nop) + ) + ) + |} + in + let*! initial_tree = initial_tree ~from_binary:false module_ in + let*! initial_tree = eval_until_input_requested initial_tree in + let+ (_ : Wasm.tree) = + List.fold_left_i_es + (fun level tree input_number -> + let*! tree = test_set_inputs input_number (Int32.of_int level) tree in + let*! info_during_inputs = Wasm.get_info tree in + (* Right after `set_input`, no new input is required and the current + phase is Collect. *) + assert (info_during_inputs.input_request = No_input_required) ; + + let*! tree = eval_to_snapshot tree in + let*! info_end_of_inputs = Wasm.get_info tree in + (* We evaluate until a snapshot, we now go into the `Eval` phase. *) + assert (info_end_of_inputs.input_request = No_input_required) ; + + (* We finally evaluate an `Eval` phase that doesn't ask for reboot: it + should now wait for inputs and be in `Collect` phase. *) + let*! tree = eval_to_snapshot tree (* Snapshot *) in + let*! tree = Wasm.compute_step tree (* Collect *) in + let*! info_after_eval = Wasm.get_info tree in + assert (info_after_eval.input_request = Input_required) ; + return tree) + initial_tree + input_numbers + in + () + +let test_scheduling_one_inbox () = test_scheduling_multiple_inboxes [10] + +let test_scheduling_five_inboxes () = + test_scheduling_multiple_inboxes [10; 5; 15; 8; 2] + let tests = [ tztest @@ -1401,4 +1496,12 @@ let tests = ~binary:false ~error:`Init "(module (memory 1))"); + tztest + "Test scheduling with 10 inputs in a unique inbox" + `Quick + test_scheduling_one_inbox; + tztest + "Test scheduling with 5 inboxes with a different input number" + `Quick + test_scheduling_five_inboxes; ] diff --git a/src/lib_scoru_wasm/test/test_wasm_vm.ml b/src/lib_scoru_wasm/test/test_wasm_vm.ml index bea52844c87820c882805cc37b4b8c4a37ab1862..afd473788dbbac4dac60bca031f8c06ac87d5f90 100644 --- a/src/lib_scoru_wasm/test/test_wasm_vm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_vm.ml @@ -17,7 +17,8 @@ let test_padding_state () = |} in let*! tree = initial_tree ~from_binary:false module_ in - let*! tree = set_input_step "test" 0 tree in + let*! tree = eval_until_input_requested tree in + let*! tree = set_empty_inbox_step 0l tree in let*! pvm_state = Test_encodings_util.Tree_encoding_runner.decode Tezos_scoru_wasm.Wasm_pvm.pvm_state_encoding diff --git a/src/lib_scoru_wasm/wasm_pvm.ml b/src/lib_scoru_wasm/wasm_pvm.ml index 1fe1865326f9095fe0a52f011fdc2391aeffd140..9ef338c8655c925e66f148d0f12a622538fc93d1 100644 --- a/src/lib_scoru_wasm/wasm_pvm.ml +++ b/src/lib_scoru_wasm/wasm_pvm.ml @@ -35,10 +35,10 @@ let tick_state_encoding = (value [] Data_encoding.string) [ case - "start" + "snapshot" (return ()) - (function Start -> Some () | _ -> None) - (fun () -> Start); + (function Snapshot -> Some () | _ -> None) + (fun () -> Snapshot); case "decode" Parsing.Decode.encoding @@ -95,10 +95,10 @@ let tick_state_encoding = (function Stuck err -> Some err | _ -> None) (fun err -> Stuck err); case - "snapshot" + "collect" (value [] Data_encoding.unit) - (function Snapshot -> Some () | _ -> None) - (fun () -> Snapshot); + (function Collect -> Some () | _ -> None) + (fun () -> Collect); case "padding" (value [] Data_encoding.unit) @@ -125,7 +125,10 @@ let pvm_state_encoding = last_input_info; current_tick; reboot_counter = - Option.value ~default:maximum_reboots_per_input reboot_counter; + Option.value + ~default:(Z.succ maximum_reboots_per_input) + (* One is used to read the inbox *) + reboot_counter; durable; buffers = (*`Gather_floppies` uses `get_info`, that decodes the state of the @@ -212,11 +215,11 @@ module Make_pvm (Wasm_vm : Wasm_vm_sig.S) (T : Tezos_tree_encoding.TREE) : bs tree - let compute_step_many ~max_steps tree = + let compute_step_many ?stop_at_snapshot ~max_steps tree = let open Lwt.Syntax in let* pvm_state = decode tree in let* pvm_state, executed_ticks = - Wasm_vm.compute_step_many ~max_steps pvm_state + Wasm_vm.compute_step_many ?stop_at_snapshot ~max_steps pvm_state in let+ tree = encode pvm_state tree in (tree, executed_ticks) @@ -295,6 +298,14 @@ module Make_pvm (Wasm_vm : Wasm_vm_sig.S) (T : Tezos_tree_encoding.TREE) : let pvm_state = {pvm_state with maximum_reboots_per_input = n} in Tree_encoding_runner.encode pvm_state_encoding pvm_state tree + let decr_reboot_counter tree = + let open Lwt_syntax in + let* pvm_state = Tree_encoding_runner.decode pvm_state_encoding tree in + let pvm_state = + {pvm_state with reboot_counter = Z.pred pvm_state.reboot_counter} + in + Tree_encoding_runner.encode pvm_state_encoding pvm_state tree + let reset_reboot_counter tree = let open Lwt_syntax in let* pvm_state = Tree_encoding_runner.decode pvm_state_encoding tree in @@ -313,12 +324,14 @@ module Make_pvm (Wasm_vm : Wasm_vm_sig.S) (T : Tezos_tree_encoding.TREE) : let+ pvm = Tree_encoding_runner.decode pvm_state_encoding tree in pvm.buffers.input - let compute_step_many_with_hooks ?after_fast_exec ~max_steps tree = + let compute_step_many_with_hooks ?after_fast_exec ?stop_at_snapshot + ~max_steps tree = let open Lwt.Syntax in let* pvm_state = Tree_encoding_runner.decode pvm_state_encoding tree in let* pvm_state, ticks = Wasm_vm.Internal_for_tests.compute_step_many_with_hooks ?after_fast_exec + ?stop_at_snapshot ~max_steps pvm_state in diff --git a/src/lib_scoru_wasm/wasm_pvm_sig.ml b/src/lib_scoru_wasm/wasm_pvm_sig.ml index 200ba33c1ca56e0ee1d5ee81e72c5ff964f68b09..21623ab7121c9f3dd326ecac336965582ee6b14e 100644 --- a/src/lib_scoru_wasm/wasm_pvm_sig.ml +++ b/src/lib_scoru_wasm/wasm_pvm_sig.ml @@ -41,6 +41,8 @@ module type Internal_for_tests = sig val set_maximum_reboots_per_input : Z.t -> tree -> tree Lwt.t + val decr_reboot_counter : tree -> tree Lwt.t + val reset_reboot_counter : tree -> tree Lwt.t val get_input_buffer : diff --git a/src/lib_scoru_wasm/wasm_pvm_state.ml b/src/lib_scoru_wasm/wasm_pvm_state.ml index 100a8f9cf71d19e6fff542982f2c8a3f9c0edbb5..5723e363ddcc646ec7fdfbe38234fc07ad830166 100644 --- a/src/lib_scoru_wasm/wasm_pvm_state.ml +++ b/src/lib_scoru_wasm/wasm_pvm_state.ml @@ -72,19 +72,21 @@ module Internal_state = struct {[ stateDiagram - Snapshot --> Restarting the machine - Start --> Decode - Decode --> Link - Link --> Init - Init --> Eval - - Eval --> Padding : Evaluation succeeded - Padding --> Snapshot : End of top-level cycle - Eval --> Stuck : Evaluation failed + Snapshot --> Collect : reboot_flag is not set + Snapshot --> Evaluation : reboot_flag is set + state Evaluation { + Decode --> Link + Link --> Init + Init --> Eval + } + Evaluation --> Padding : evaluation succeeded + Collect --> Padding + Padding --> Snapshot + Evaluation --> Stuck : something went wrong ]} *) type tick_state = - | Start + | Snapshot | Decode of Tezos_webassembly_interpreter.Decode.decode_kont | Link of { ast_module : Tezos_webassembly_interpreter.Ast.module_; @@ -103,9 +105,9 @@ module Internal_state = struct config : Tezos_webassembly_interpreter.Eval.config; module_reg : Tezos_webassembly_interpreter.Instance.module_reg; } + | Collect | Stuck of Wasm_pvm_errors.t | Padding - | Snapshot type pvm_state = { last_input_info : input_info option; (** Info about last read input. *) @@ -122,5 +124,10 @@ module Internal_state = struct (** Number of reboots between two inputs. *) } - type computation_status = Starting | Restarting | Running | Failing | Reboot + type computation_status = + | Restarting + | Forcing_restart + | Running + | Failing + | Reboot end diff --git a/src/lib_scoru_wasm/wasm_vm.ml b/src/lib_scoru_wasm/wasm_vm.ml index a5fa728bc3a427d1a242b729c75a16dd408f038a..66b731fbc9339c9a853202c31578add553c71a48 100644 --- a/src/lib_scoru_wasm/wasm_vm.ml +++ b/src/lib_scoru_wasm/wasm_vm.ml @@ -33,7 +33,7 @@ let eval_has_finished = function | Eval {config = {step_kont = Wasm.Eval.(SK_Result _); _}; _} -> true | Padding -> true (* explicit pattern matching to avoid new states introducing silent bugs *) - | Start | Decode _ | Link _ | Init _ | Eval _ | Snapshot | Stuck _ -> false + | Snapshot | Decode _ | Link _ | Init _ | Eval _ | Collect | Stuck _ -> false let ticks_to_snapshot {current_tick; last_top_level_call; max_nb_ticks; _} = let open Z in @@ -58,12 +58,12 @@ let has_stuck_flag durable = let+ stuck = Durable.(find_value durable Constants.stuck_flag_key) in Option.is_some stuck -let mark_for_reboot reboot_counter durable = +let mark_for_reboot {reboot_counter; durable; _} = let open Lwt_syntax in - if Z.Compare.(reboot_counter <= Z.zero) then return Failing - else - let+ has_reboot_flag = has_reboot_flag durable in - if has_reboot_flag then Reboot else Restarting + let+ has_reboot_flag = has_reboot_flag durable in + if has_reboot_flag then + if Z.Compare.(reboot_counter <= Z.zero) then `Forcing_restart else `Reboot + else `Restarting (* Returns true is a fallback kernel is available, and it's different to the currently running kernel. *) @@ -117,29 +117,20 @@ let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = return ~durable Padding else return ~status:Failing (Stuck (No_fallback_kernel cause)) | Stuck e -> return ~status:Failing (Stuck e) - | Snapshot -> - let* has_reboot_flag = has_reboot_flag durable in - if has_reboot_flag then - let* durable = Durable.(delete durable Constants.reboot_flag_key) in - return ~durable Start - else - return - ~status:Failing - (Stuck - (Wasm_pvm_errors.invalid_state "snapshot is an input tick state")) - | Padding when is_time_for_snapshot pvm_state -> - (* The state is Padding which means that either the calculation finished - or it was trapped. Since we are at snapshot time we will either return - a stuck because too many reboots happened if the flag has been set or - snapshot. *) - let* status = mark_for_reboot pvm_state.reboot_counter durable in - (* Execution took too many reboot *) - if status = Failing then return ~status (Stuck Too_many_reboots) - else return ~status Snapshot + | Snapshot -> ( + let* reboot_status = mark_for_reboot pvm_state in + match reboot_status with + | `Reboot -> return ~status:Reboot ~durable (initial_boot_state ()) + | `Forcing_restart -> return ~status:Forcing_restart ~durable Collect + | `Restarting -> return ~status:Restarting ~durable Collect) + | Collect -> + return + ~status:Failing + (Stuck (Wasm_pvm_errors.invalid_state "Collect is a input tick")) + | Padding when is_time_for_snapshot pvm_state -> return ~durable Snapshot | _ when is_time_for_snapshot pvm_state -> (* Execution took too many ticks *) return ~status:Failing (Stuck Too_many_ticks) - | Start -> return (initial_boot_state ()) | Decode {module_kont = MKStop ast_module; _} -> return (Link @@ -203,7 +194,7 @@ let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = (* Set kernel - now known to be valid - as fallback kernel, if it is not already *) let* durable = save_fallback_kernel durable in - return ~durable ~status:Starting (Eval {config; module_reg}) + return ~durable (Eval {config; module_reg}) | _ -> (* We require a function with the name [main] to be exported rather than any other structure. *) @@ -225,24 +216,24 @@ let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = init_kont in return (Init {self; ast_module; init_kont; module_reg}) - | _ when eval_has_finished tick_state -> - (* We have an empty set of admin instructions, but need to wait until we can restart *) - let* has_stuck_flag = has_stuck_flag durable in - if has_stuck_flag then - let* durable = Durable.(delete durable Constants.stuck_flag_key) in - return ~durable Padding - else return Padding + | Padding -> return Padding | Eval {config = {step_kont = Wasm.Eval.(SK_Trapped _msg); _}; _} -> let* durable = Durable.write_value_exn - durable ~edit_readonly:true + durable Constants.stuck_flag_key 0L "" in return ~durable Padding - | Padding -> return Padding + | _ when eval_has_finished tick_state -> + (* We have an empty set of admin instructions, but need to wait until we can restart *) + let* has_stuck_flag = has_stuck_flag durable in + if has_stuck_flag then + let* durable = Durable.(delete durable Constants.stuck_flag_key) in + return ~durable Padding + else return Padding | Eval {config; module_reg} -> (* Continue execution. *) let store = Durable.to_storage durable in @@ -263,7 +254,7 @@ let next_tick_state pvm_state = | Link _ -> Link_error error.Wasm_pvm_errors.raw_exception | Init _ -> Init_error error | Eval _ -> Eval_error error - | Start | Stuck _ | Snapshot | Padding -> + | Snapshot | Stuck _ | Collect | Padding -> Unknown_error error.raw_exception) | `Unknown raw_exception -> Unknown_error raw_exception in @@ -272,14 +263,49 @@ let next_tick_state pvm_state = Lwt.catch (fun () -> unsafe_next_tick_state pvm_state) to_stuck let next_last_top_level_call {current_tick; last_top_level_call; _} = function - | Restarting | Reboot -> Z.succ current_tick - | Starting | Failing | Running -> last_top_level_call + | Forcing_restart | Restarting | Reboot -> current_tick + | Failing | Running -> last_top_level_call let next_reboot_counter {reboot_counter; maximum_reboots_per_input; _} status = match status with | Reboot -> Z.pred reboot_counter - | Restarting | Failing -> maximum_reboots_per_input - | Starting | Running -> reboot_counter + | Forcing_restart | Restarting | Failing -> + Z.succ maximum_reboots_per_input (* one is used to read the inbox *) + | Running -> reboot_counter + +(** When successfully restarting the VM, we can remove the + [Too_many_reboot] flag if it exists. On the contrary, we create it + in case we are failing. *) +let patch_too_many_reboot_flag durable = + let open Lwt_syntax in + function + | Restarting -> + Durable.delete + ~edit_readonly:true + durable + Constants.too_many_reboot_flag_key + | Forcing_restart -> + Durable.( + write_value_exn + ~edit_readonly:true + durable + Constants.too_many_reboot_flag_key + 0L + "") + | _ -> return durable + +(** When rebooting, we can remove the [Reboot] flag (because it has + achieved its purpose). On the contrary, when [Restarting] or + [Forcing_restart], we set the flag, because we will want to reboot + once the inbox is loaded. *) +let patch_reboot_flag durable = + let open Lwt_syntax in + function + | Reboot -> + Durable.delete ~edit_readonly:true durable Constants.reboot_flag_key + | Forcing_restart | Restarting -> + Durable.(write_value_exn durable Constants.reboot_flag_key 0L "") + | _ -> return durable (** [compute_step pvm_state] does one computation step on [pvm_state]. Returns the new state. @@ -291,6 +317,8 @@ let compute_step pvm_state = 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 reboot_counter = next_reboot_counter pvm_state status in + let* durable = patch_too_many_reboot_flag durable status in + let* durable = patch_reboot_flag durable status in let pvm_state = { pvm_state with @@ -304,18 +332,15 @@ let compute_step pvm_state = return pvm_state let input_request pvm_state = - let open Lwt_syntax in match pvm_state.tick_state with - | Stuck _ -> return Wasm_pvm_state.Input_required - | Snapshot -> - let+ has_reboot_flag = has_reboot_flag pvm_state.durable in - if has_reboot_flag then Wasm_pvm_state.No_input_required - else Wasm_pvm_state.Input_required + | Stuck _ -> Wasm_pvm_state.Input_required + | Snapshot -> Wasm_pvm_state.No_input_required + | Collect -> Wasm_pvm_state.Input_required | Eval {config; _} -> ( match Tezos_webassembly_interpreter.Eval.is_reveal_tick config with - | Some reveal -> return (Wasm_pvm_state.Reveal_required reveal) - | None -> return Wasm_pvm_state.No_input_required) - | _ -> return Wasm_pvm_state.No_input_required + | Some reveal -> Wasm_pvm_state.Reveal_required reveal + | None -> Wasm_pvm_state.No_input_required) + | _ -> Wasm_pvm_state.No_input_required let is_top_level_padding pvm_state = eval_has_finished pvm_state.tick_state && not (is_time_for_snapshot pvm_state) @@ -363,14 +388,20 @@ let compute_step_many_until ?(max_steps = 1L) should_continue = measure_executed_ticks one_or_more_steps let should_compute pvm_state = - let open Lwt.Syntax in - let+ input_request_val = input_request pvm_state in + let input_request_val = input_request pvm_state in match input_request_val with | Reveal_required _ | Input_required -> false | No_input_required -> true -let compute_step_many ~max_steps pvm_state = - compute_step_many_until ~max_steps should_compute pvm_state +let compute_step_many ?(stop_at_snapshot = false) ~max_steps pvm_state = + compute_step_many_until + ~max_steps + (fun pvm_state -> + Lwt.return + (* should_compute && (stop_at_snapshot -> tick_state <> snapshot) *) + (should_compute pvm_state + && ((not stop_at_snapshot) || pvm_state.tick_state <> Snapshot))) + pvm_state let set_input_step input_info message pvm_state = let open Lwt_syntax in @@ -379,18 +410,17 @@ let set_input_step input_info message pvm_state = let raw_level = Bounded.Non_negative_int32.to_value inbox_level in let+ tick_state = match pvm_state.tick_state with - | Snapshot -> + | Collect -> ( let+ () = Wasm.Input_buffer.( enqueue pvm_state.buffers.input {raw_level; message_counter; payload = String.to_bytes message}) in - (* TODO: https://gitlab.com/tezos/tezos/-/issues/3157 - The goal is to read a complete inbox. *) - (* Go back to decoding *) - Start - | Start -> + match Pvm_input_kind.from_raw_input message with + | Internal End_of_level -> Padding + | _ -> Collect) + | Snapshot -> Lwt.return (Stuck (Wasm_pvm_errors.invalid_state "No input required during start")) @@ -427,7 +457,7 @@ let reveal_step payload pvm_state = | Eval {config; module_reg} -> let* config = Eval.reveal_step module_reg payload config in return (Eval {config; module_reg}) - | Start -> + | Snapshot -> return (Stuck (Wasm_pvm_errors.invalid_state "No reveal expected during start")) | Decode _ -> @@ -442,11 +472,11 @@ let reveal_step payload pvm_state = (Stuck (Wasm_pvm_errors.invalid_state "No reveal expected during initialization")) - | Snapshot -> + | Collect -> return (Stuck (Wasm_pvm_errors.invalid_state - "No reveal expected during snapshotting")) + "No reveal expected during collecting")) | Stuck _ | Padding -> return pvm_state.tick_state let get_output output_info output = @@ -459,9 +489,10 @@ let get_output output_info output = let get_info ({current_tick; last_input_info; _} as pvm_state) = let open Lwt_syntax in - let+ input_request = input_request pvm_state in - Wasm_pvm_state. - {current_tick; last_input_read = last_input_info; input_request} + let input_request = input_request pvm_state in + return + @@ Wasm_pvm_state. + {current_tick; last_input_read = last_input_info; input_request} module Internal_for_tests = struct let compute_step_many_with_hooks ?after_fast_exec:_ = compute_step_many diff --git a/src/lib_scoru_wasm/wasm_vm.mli b/src/lib_scoru_wasm/wasm_vm.mli index aa082eb767c81b8e54f1cd65878f9de28acf30a8..28bfb33f336ea01b52cf3389fb868ce65719b821 100644 --- a/src/lib_scoru_wasm/wasm_vm.mli +++ b/src/lib_scoru_wasm/wasm_vm.mli @@ -51,14 +51,15 @@ val eval_has_finished : tick_state -> bool (** [should_compute pvm_state] probes whether it is possible to continue with more computational steps. *) -val should_compute : pvm_state -> bool Lwt.t +val should_compute : pvm_state -> bool (** [has_reboot_flag durable] checks if the reboot flag is set in the durable storage. *) val has_reboot_flag : Durable.t -> bool Lwt.t (** [mark_for_reboot reboot_counter durable] figures out the computational status with respect to what the PVM shall do next. E.g. schedule a reboot. *) -val mark_for_reboot : Z.t -> Durable.t -> computation_status Lwt.t +val mark_for_reboot : + pvm_state -> [`Forcing_restart | `Reboot | `Restarting] Lwt.t (** [next_reboot_counter pvm_state status] computes the next reboot counter. *) val next_reboot_counter : pvm_state -> computation_status -> Z.t diff --git a/src/lib_scoru_wasm/wasm_vm_sig.ml b/src/lib_scoru_wasm/wasm_vm_sig.ml index ee94144a3b257e6e05af95db2aa62e8f172e87d3..011385c71914fd3387c6a3be238819962222e065 100644 --- a/src/lib_scoru_wasm/wasm_vm_sig.ml +++ b/src/lib_scoru_wasm/wasm_vm_sig.ml @@ -31,6 +31,7 @@ module type Internal_for_tests = sig val compute_step_many_with_hooks : ?after_fast_exec:(unit -> unit) -> + ?stop_at_snapshot:bool -> max_steps:int64 -> state -> (state * int64) Lwt.t @@ -47,7 +48,8 @@ module type Generic = sig than one tick, but its resulting pvm_state will be stricly equivalent. Returns a tuple containing the number of executed ticks and the new pvm_state. *) - val compute_step_many : max_steps:int64 -> state -> (state * int64) Lwt.t + val compute_step_many : + ?stop_at_snapshot:bool -> max_steps:int64 -> state -> (state * int64) Lwt.t (** [compute_step pvm_state] forwards the VM by one compute tick. If the VM is expecting input, it gets stuck. If the VM is already stuck, this function diff --git a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml index fb24246a759094d57307977f47584235f70bdad1..0c2f57a5018ab1990c1d3f8a372880e9cc907137 100644 --- a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml @@ -57,7 +57,8 @@ module Impl : Pvm.S = struct | Parsing -> "Parsing" | Evaluating -> "Evaluating" - let eval_many ~max_steps initial_state = + let eval_many ?stop_at_snapshot ~max_steps initial_state = + ignore stop_at_snapshot ; let rec go state step = let open Lwt.Syntax in let* is_input_required = is_input_state state in diff --git a/src/proto_alpha/bin_sc_rollup_node/pvm.ml b/src/proto_alpha/bin_sc_rollup_node/pvm.ml index a73f7cd3488f440fba59c4005889162afda8973c..7dd45d4e2ccc498af84cae4b9281a8f8833643fa 100644 --- a/src/proto_alpha/bin_sc_rollup_node/pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/pvm.ml @@ -52,7 +52,8 @@ module type S = sig (** [eval_many ~max_steps s0] returns a state [s1] resulting from the execution of up to [~max_steps] steps of the rollup at state [s0]. *) - val eval_many : max_steps:int64 -> state -> (state * int64) Lwt.t + val eval_many : + ?stop_at_snapshot:bool -> max_steps:int64 -> state -> (state * int64) Lwt.t (** State storage for this PVM. *) module State : sig diff --git a/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml index 92dde90354fc96501c2217d4e7cba6c78d99f63d..c4376b1caaae4bbe001fc5a600b03437ea4a8f55 100644 --- a/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml @@ -81,7 +81,7 @@ module Impl : Pvm.S = struct module Backend = Make_backend (Wasm_2_0_0_proof_format.Tree) - let eval_many = Backend.compute_step_many + let eval_many ?stop_at_snapshot = Backend.compute_step_many ?stop_at_snapshot end include Impl diff --git a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_wasm.ml b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_wasm.ml index 26e41fc992bc0c9fbeef6b1c84cd28122780f40a..026fad42b561141a6ad979793150a0d16dd8cd26 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_wasm.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_wasm.ml @@ -119,6 +119,22 @@ let test_metadata_size () = = Tezos_scoru_wasm.Host_funcs.Internal_for_tests.metadata_size) ; Lwt_result_syntax.return_unit +let test_l1_input_kind () = + let open Lwt_result_syntax in + let open Sc_rollup_inbox_message_repr in + let open Tezos_scoru_wasm in + let check_msg msg expected = + let*? msg = Environment.wrap_tzresult @@ serialize msg in + let msg = unsafe_to_string msg |> Pvm_input_kind.from_raw_input in + assert (msg = expected) ; + return_unit + in + let* () = check_msg (Internal Start_of_level) (Internal Start_of_level) in + let* () = check_msg (Internal End_of_level) (Internal End_of_level) in + let* () = check_msg (External "payload") External in + + return_unit + let make_transaction value text contract = let entrypoint = Entrypoint_repr.default in let destination : Contract_hash.t = @@ -144,14 +160,16 @@ let make_transactions () = in List.map (fun (contract, i, s) -> make_transaction i s contract) l -(* This is simple "echo kernel" it spits out whatever it receive. It uses the - [write_output] host function and so it is used to test this function. *) +(* This is simple "echo kernel" it spits out the first three inputs (SOL, input, + EOL) it receives. It uses the [write_output] host function and so it is used + to test this function. *) let test_output () = let open Lwt_result_syntax in let level_offset = 20 in let id_offset = 40 in let dst = 60 in let max_bytes = 3600 in + let dst_without_header = dst + 2 in let modul = Format.sprintf {| @@ -163,13 +181,27 @@ let test_output () = (import "rollup_safe_core" "write_output" (func $write_output (type $t0))) (func (export "kernel_next") (local $size i32) - (local.set $size (call $read_input - (i32.const %d) - (i32.const %d) - (i32.const %d) - (i32.const %d))) + (local.set $size (call $read_input + (i32.const %d) + (i32.const %d) + (i32.const %d) + (i32.const %d))) (call $write_output (i32.const %d) - (local.get $size)) + (i32.sub (local.get $size) (i32.const 2))) + (local.set $size (call $read_input + (i32.const %d) + (i32.const %d) + (i32.const %d) + (i32.const %d))) + (call $write_output (i32.const %d) + (i32.sub (local.get $size) (i32.const 2))) + (local.set $size (call $read_input + (i32.const %d) + (i32.const %d) + (i32.const %d) + (i32.const %d))) + (call $write_output (i32.const %d) + (local.get $size)) drop) (memory (;0;) 17) (export "memory" (memory 0)) @@ -180,8 +212,19 @@ let test_output () = id_offset dst max_bytes + dst_without_header + level_offset + id_offset dst + max_bytes + dst_without_header + level_offset + id_offset + dst + max_bytes + dst_without_header in + let*! dummy = Context.init "/tmp" in let dummy_context = Context.empty dummy in let*! (empty_tree : Wasm.tree) = Test_encodings_util.empty_tree () in @@ -203,12 +246,17 @@ let test_output () = Sc_rollup_outbox_message_repr.encoding out in - let*! tree = set_input_step string_input_message 0 tree in + let*! tree = eval_until_input_requested tree in + let*! tree = set_full_input_step [string_input_message] 0l tree in let*! final_tree = eval_until_input_requested tree in let*! output = Wasm.Internal_for_tests.get_output_buffer final_tree in - let*! level, message_index = + let*! level, end_of_level_message_index = Tezos_webassembly_interpreter.Output_buffer.get_id output in + (* The last message in the outbox corresponds to EOL, due to the nature of the + kernel. As such we must take the one preceding it. *) + let message_index = Z.pred end_of_level_message_index in + let*! bytes_output_message = Tezos_webassembly_interpreter.Output_buffer.get output level message_index in @@ -239,5 +287,6 @@ let tests = `Quick test_initial_state_hash_wasm_pvm; Tztest.tztest "size of a rollup metadata" `Quick test_metadata_size; + Tztest.tztest "l1 input kind" `Quick test_l1_input_kind; Tztest.tztest "test output proofs" `Quick test_output; ] diff --git a/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - node advances PVM state with messages (external).out b/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - node advances PVM state with messages (external).out index a482518914b73d7693a4ac2968bb2708640be389..764f9940dd6fdbe23fdfe390748f693d75088762 100644 --- a/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - node advances PVM state with messages (external).out +++ b/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - node advances PVM state with messages (external).out @@ -84,13 +84,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"5" +"11000000002" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"5" +"11000000002" ./octez-client --wait none send sc rollup message '["2 8 + value"]' from bootstrap2 Node is bootstrapped. @@ -133,13 +133,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"8" +"11000000005" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"8" +"11000000005" ./octez-client --wait none send sc rollup message '["3 10 + value"]' from bootstrap2 Node is bootstrapped. @@ -183,13 +183,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"11" +"11000000008" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"11" +"11000000008" ./octez-client --wait none send sc rollup message '["4 12 + value"]' from bootstrap2 Node is bootstrapped. @@ -233,13 +233,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"14" +"11000000011" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"14" +"11000000011" ./octez-client --wait none send sc rollup message '["5 14 + value"]' from bootstrap2 Node is bootstrapped. @@ -283,13 +283,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"17" +"11000000014" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"17" +"11000000014" ./octez-client --wait none send sc rollup message '["6 16 + value"]' from bootstrap2 Node is bootstrapped. @@ -333,13 +333,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"20" +"11000000017" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"20" +"11000000017" ./octez-client --wait none send sc rollup message '["7 18 + value"]' from bootstrap2 Node is bootstrapped. @@ -384,13 +384,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"23" +"11000000020" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"23" +"11000000020" ./octez-client --wait none send sc rollup message '["8 20 + value"]' from bootstrap2 Node is bootstrapped. @@ -436,13 +436,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"26" +"11000000023" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"26" +"11000000023" ./octez-client --wait none send sc rollup message '["9 22 + value"]' from bootstrap2 Node is bootstrapped. @@ -488,13 +488,13 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"29" +"11000000026" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"29" +"11000000026" ./octez-client --wait none send sc rollup message '["10 24 + value"]' from bootstrap2 Node is bootstrapped. @@ -540,4 +540,4 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"32" +"11000000029" diff --git a/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - node advances PVM state with messages (internal).out b/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - node advances PVM state with messages (internal).out index 47a24ef31cb7d646daaa0e3a2b4b4c0376ee8a64..fe876474927dbf0fdb1eed37c2501e00b1144eaa 100644 --- a/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - node advances PVM state with messages (internal).out +++ b/tezt/tests/expected/sc_rollup.ml/Alpha- wasm_2_0_0 - node advances PVM state with messages (internal).out @@ -41,118 +41,118 @@ This sequence of operations was run: "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"4" +"11000000002" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"7" +"11000000005" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"7" +"11000000005" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"10" +"11000000008" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"10" +"11000000008" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"13" +"11000000011" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"13" +"11000000011" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"16" +"11000000014" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"16" +"11000000014" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"19" +"11000000017" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"19" +"11000000017" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"22" +"11000000020" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"22" +"11000000020" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"25" +"11000000023" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"25" +"11000000023" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"28" +"11000000026" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"28" +"11000000026" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"31" +"11000000029" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"31" +"11000000029" ./octez-sc-rollup-client-alpha rpc get /global/block/head/state_hash "[SC_ROLLUP_PVM_STATE_HASH]" ./octez-sc-rollup-client-alpha rpc get /global/block/head/total_ticks -"34" +"11000000032" diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 9c4a6b1b8919de9a6025c78313015917a5604aac..efd9dc83f12f912b6cf46d8fee56142edf304b6d 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -772,7 +772,7 @@ let test_rollup_node_boots_into_initial_state ~kind = let expected_status = match kind with | "arith" -> "Halted" - | "wasm_2_0_0" -> "Waiting for input message" + | "wasm_2_0_0" -> "Computing" | _ -> raise (Invalid_argument kind) in Check.(status = expected_status) @@ -1501,15 +1501,23 @@ let commitments_reorgs ~kind sc_rollup_node sc_rollup_client sc_rollup node "Commitment has been stored at a level different than expected (%L = %R)" ; let () = Log.info "init_level: %d" init_level in (let stored_number_of_ticks = Option.map number_of_ticks stored_commitment in - let additional_ticks = + let expected_number_of_ticks = match kind with - | "arith" -> 1 (* boot sector *) + 1 (* metadata *) - | "wasm_2_0_0" -> 1 (* boot_sector *) + 1 (* moving through start state *) + | "arith" -> + 1 (* boot sector *) + 1 (* metadata *) + (2 * levels_to_commitment) + (* input ticks *) + | "wasm_2_0_0" -> + 11_000_000_000 + (* ticks to load first inbox commitments: + snapshot --> collect --> padding --> snapshot + see Lib_scoru_wasm.Constants.wasm_max_tick *) + + 1 (* snapshot --> decode *) + + 1 (* decode -> stuck *) + + (2 * (levels_to_commitment - 1)) + (* input ticks for the rest of the inboxes *) | _ -> assert false in - Check.( - stored_number_of_ticks - = Some ((2 * levels_to_commitment) + additional_ticks)) + Check.(stored_number_of_ticks = Some expected_number_of_ticks) (Check.option Check.int) ~error_msg: "Number of ticks processed by commitment is different from the number \