From 5b8fa5da59b768604c035ccecc988d7a6ca9ed4d Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 24 Oct 2022 13:59:16 +0200 Subject: [PATCH 1/4] WASM/PVM: add representation of input from L1 This representation can be thought of a half-deserialized version of [Sc_rollup_inbox_message_repr.t]. This type cannot be used as is from the protocol, since `lib_scoru_wasm` lives under the environment barrier, and the protocol is above. Moreover, some of its values are serialized types that does not exist outside of the protocol (Contract and rollups hash for example), which makes it difficult to write encoders and decoders for it. We take the approach to simply read the first two bytes of the input to discriminate the input and leaves deserialization to the kernel. --- src/lib_scoru_wasm/pvm_input_kind.ml | 80 +++++++++++++++++++ src/lib_scoru_wasm/pvm_input_kind.mli | 55 +++++++++++++ .../test/unit/test_sc_rollup_wasm.ml | 17 ++++ 3 files changed, 152 insertions(+) create mode 100644 src/lib_scoru_wasm/pvm_input_kind.ml create mode 100644 src/lib_scoru_wasm/pvm_input_kind.mli 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 000000000000..16f668130635 --- /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 000000000000..479653dab0ec --- /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/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 26e41fc992bc..b25864e1c653 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 = @@ -239,5 +255,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; ] -- GitLab From 60d5cf7693d53049fbca94e49560e76ae7275008 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 8 Nov 2022 14:25:04 +0100 Subject: [PATCH 2/4] WASM/PVM: change number of reboot to 10_000 (placeholder) Necessary to have a bigger value due to the scheduling changes --- src/lib_scoru_wasm/constants.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib_scoru_wasm/constants.ml b/src/lib_scoru_wasm/constants.ml index e8a9f6bfef2a..03a7a8ee1d1a 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. *) -- GitLab From 8d120dfda79a0c454f37a071f0c7b326f0ba65ea Mon Sep 17 00:00:00 2001 From: Thomas Letan Date: Tue, 15 Nov 2022 14:34:07 +0100 Subject: [PATCH 3/4] WASM: Load a full inbox level at once, then pad with no-op ticks - Initial state is `Snapshot` - When `Snapshot` - no reboot is asked, set a reboot request, and goto `Collect` - a reboot is asked, check it is authorized - Set the too many reboot flag if the reboot is not authorized and goto `Collect` - Otherwise, goto `Decode` - From `Collect`, goto `Padding` when reaching the max ticks - From `Eval`, some thing, but set the trapped flag if necessary, or clear it in case `kernel_next` has succeeded - From `Padding`, clear the `too_many_reboot` flag, then goto `Snapshot` when the time has come This means that the Snapshot state is no longer an input state, which also means that the heuristic for stopping `compute_step_many` had to be updated. Co-authored-by: Pierrick Couderc --- src/lib_scoru_wasm/bench/exec.ml | 2 +- src/lib_scoru_wasm/bench/exec.mli | 2 +- src/lib_scoru_wasm/bench/scenario.ml | 4 +- src/lib_scoru_wasm/constants.ml | 3 + src/lib_scoru_wasm/durable.ml | 4 +- src/lib_scoru_wasm/durable.mli | 15 +- src/lib_scoru_wasm/fast/vm.ml | 79 +++++---- src/lib_scoru_wasm/test/helpers/wasm_utils.ml | 85 ++++++++- src/lib_scoru_wasm/test/test_fast.ml | 14 +- .../test/test_fixed_nb_ticks.ml | 51 +++--- src/lib_scoru_wasm/test/test_get_set.ml | 19 +- .../test/test_hash_consistency.ml | 4 +- src/lib_scoru_wasm/test/test_init.ml | 26 +-- src/lib_scoru_wasm/test/test_reveal.ml | 8 +- src/lib_scoru_wasm/test/test_wasm_pvm.ml | 105 ++++++----- src/lib_scoru_wasm/test/test_wasm_vm.ml | 3 +- src/lib_scoru_wasm/wasm_pvm.ml | 33 ++-- src/lib_scoru_wasm/wasm_pvm_sig.ml | 2 + src/lib_scoru_wasm/wasm_pvm_state.ml | 31 ++-- src/lib_scoru_wasm/wasm_vm.ml | 163 +++++++++++------- src/lib_scoru_wasm/wasm_vm.mli | 5 +- src/lib_scoru_wasm/wasm_vm_sig.ml | 4 +- .../bin_sc_rollup_node/arith_pvm.ml | 3 +- src/proto_alpha/bin_sc_rollup_node/pvm.ml | 3 +- .../bin_sc_rollup_node/wasm_2_0_0_pvm.ml | 2 +- .../test/unit/test_sc_rollup_wasm.ml | 52 ++++-- ...ces PVM state with messages (external).out | 38 ++-- ...ces PVM state with messages (internal).out | 40 ++--- tezt/tests/sc_rollup.ml | 22 ++- 29 files changed, 502 insertions(+), 320 deletions(-) diff --git a/src/lib_scoru_wasm/bench/exec.ml b/src/lib_scoru_wasm/bench/exec.ml index 81317742264f..34196e72802c 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 1fcbb840a6e4..1701a1392812 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 5da4108557e6..5b2b934ed626 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 03a7a8ee1d1a..d928c276110c 100644 --- a/src/lib_scoru_wasm/constants.ml +++ b/src/lib_scoru_wasm/constants.ml @@ -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 f1f625d4c7a4..d797a25b37f2 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 0bbd607f4da6..3cc60c80f0d4 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 03bd393ee921..c8dd2c40d254 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/test/helpers/wasm_utils.ml b/src/lib_scoru_wasm/test/helpers/wasm_utils.ml index 4b8b60d99c28..63bc7897dd2b 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 e126bad2657e..efdd88bc6917 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 9b325b44e04c..e7a29375948b 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 f6c4b4883f2e..67c5c60bc71f 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 91a3574d53c6..594269d1b121 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 a096a4fd12c5..b6b965187e7c 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 c3c186c0b634..3155543cc67f 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 1e5f63f78cec..d25155715344 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 diff --git a/src/lib_scoru_wasm/test/test_wasm_vm.ml b/src/lib_scoru_wasm/test/test_wasm_vm.ml index bea52844c878..afd473788dbb 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 1fe1865326f9..9ef338c8655c 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 200ba33c1ca5..21623ab7121c 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 100a8f9cf71d..5723e363ddcc 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 a5fa728bc3a4..66b731fbc933 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 aa082eb767c8..28bfb33f336e 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 ee94144a3b25..011385c71914 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 fb24246a7590..0c2f57a5018a 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 a73f7cd3488f..7dd45d4e2ccc 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 92dde90354fc..c4376b1caaae 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 b25864e1c653..026fad42b561 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 @@ -160,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 {| @@ -179,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)) @@ -196,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 @@ -219,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 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 a482518914b7..764f9940dd6f 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 47a24ef31cb7..fe876474927d 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 9c4a6b1b8919..efd9dc83f12f 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 \ -- GitLab From 4be4d3c8f2691ccc9efbd5ab27ddfdc90a544fe6 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Fri, 28 Oct 2022 17:04:56 +0200 Subject: [PATCH 4/4] WASM/Test: Test new PVM scheduling between input and normal ticks Co-authored-by: Thomas Letan --- src/lib_scoru_wasm/test/test_wasm_pvm.ml | 104 +++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index d25155715344..be0e909097d5 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -1325,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 @@ -1400,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; ] -- GitLab