diff --git a/src/lib_scoru_wasm/constants.ml b/src/lib_scoru_wasm/constants.ml index a4c28f9f9f297d331edd4e264420222e4d75fce7..5e5a3d356e2a367b8ca11e28cf39f51abd662b2a 100644 --- a/src/lib_scoru_wasm/constants.ml +++ b/src/lib_scoru_wasm/constants.ml @@ -54,6 +54,9 @@ 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 upgrade_error_flag_key = + Durable.key_of_string_exn "/readonly/kernel/env/upgrade_error" + let too_many_reboot_flag_key = Durable.key_of_string_exn "/readonly/kernel/env/too_many_reboot" diff --git a/src/lib_scoru_wasm/fast/exec.ml b/src/lib_scoru_wasm/fast/exec.ml index bd1d39f94efa8afcd33c7a7f1a9b7b46db94df9c..352d6cf3ea02a59ecd05fc19898d9865cdaffbf0 100644 --- a/src/lib_scoru_wasm/fast/exec.ml +++ b/src/lib_scoru_wasm/fast/exec.ml @@ -28,6 +28,8 @@ open Tezos_scoru_wasm module Wasmer = Tezos_wasmer module Lazy_containers = Tezos_lazy_containers +include (Wasm_vm : Wasm_vm_sig.S) + let store = Lazy.from_fun @@ fun () -> let engine = Wasmer.Engine.create Wasmer.Config.{compiler = SINGLEPASS} in @@ -73,7 +75,8 @@ let compute builtins durable buffers = Wasmer.Instance.delete instance ; + let* durable = Wasm_vm.patch_flags_on_eval_successful host_state.durable in (* TODO: #4283 The module is cached, but the cash is never cleaned. This is the point where it was scrubed before.*) - Lwt.return host_state.durable + Lwt.return durable diff --git a/src/lib_scoru_wasm/test/helpers/wasm_utils.ml b/src/lib_scoru_wasm/test/helpers/wasm_utils.ml index 0cf85bb3adf31c8236437b35fb65d572fc595751..2c2ff782dd7ae14ed3e7aaf4c217f01afbe9b795 100644 --- a/src/lib_scoru_wasm/test/helpers/wasm_utils.ml +++ b/src/lib_scoru_wasm/test/helpers/wasm_utils.ml @@ -237,8 +237,20 @@ let check_error ?expected_kind ?expected_reason error = match (expected_kind, error) with | Some `Decode, Wasm_pvm_errors.Decode_error {explanation; _} -> check_reason explanation + | ( Some `No_fallback_decode, + Wasm_pvm_errors.No_fallback_kernel + (Wasm_pvm_errors.Decode_cause {explanation; _}) ) -> + check_reason explanation | Some `Init, Init_error {explanation; _} -> check_reason explanation + | ( Some `No_fallback_init, + Wasm_pvm_errors.No_fallback_kernel + (Wasm_pvm_errors.Init_cause {explanation; _}) ) -> + check_reason explanation | Some `Link, Link_error explanation -> check_reason (Some explanation) + | ( Some `No_fallback_link, + Wasm_pvm_errors.No_fallback_kernel + (Wasm_pvm_errors.Link_cause explanation) ) -> + check_reason (Some explanation) | Some `Eval, Eval_error {explanation; _} -> check_reason explanation | Some `Invalid_state, Invalid_state explanation -> check_reason (Some explanation) diff --git a/src/lib_scoru_wasm/test/test_init.ml b/src/lib_scoru_wasm/test/test_init.ml index 465decf663260709086afb26d3a820d7d164b574..cf3c4593be42cbdf49f1c8490ea42678e750115e 100644 --- a/src/lib_scoru_wasm/test/test_init.ml +++ b/src/lib_scoru_wasm/test/test_init.ml @@ -38,7 +38,7 @@ let test_memory0_export () = let* stuck, _ = eval_until_stuck bad_module_tree in assert ( check_error - ~expected_kind:`Init + ~expected_kind:`No_fallback_init ~expected_reason:"Module must export memory 0" stuck) ; @@ -92,7 +92,7 @@ let test_module_name_size () = let* stuck, _ = eval_until_stuck bad_module_tree in assert ( check_error - ~expected_kind:`Decode + ~expected_kind:`No_fallback_decode ~expected_reason:"Names cannot exceed 512 bytes" stuck) ; let*! good_module_tree = initial_tree (build_module 512) in @@ -136,6 +136,12 @@ let test_imports () = ~module_name:bad_module_name ~item_name:bad_item_name in + let expected_error = + match expected_error with + | Wasm_pvm_errors.(Link_error e) -> + Wasm_pvm_errors.(No_fallback_kernel (Link_cause e)) + | _ -> assert false + in if stuck = expected_error then return_unit else failwith "Unexpected stuck state!" in @@ -153,6 +159,12 @@ let test_imports () = ~module_name:good_module_name ~item_name:bad_item_name in + let expected_error = + match expected_error with + | Wasm_pvm_errors.(Link_error e) -> + Wasm_pvm_errors.(No_fallback_kernel (Link_cause e)) + | _ -> assert false + in if stuck = expected_error then return_unit else failwith "Unexpected stuck state!" in @@ -192,7 +204,7 @@ let test_host_func_start_restriction () = let+ stuck, _ = eval_until_stuck state in assert ( check_error - ~expected_kind:`Init + ~expected_kind:`No_fallback_init ~expected_reason: "host functions must not access memory during initialisation" stuck) @@ -274,7 +286,7 @@ let test_float32_type () = Format.printf "%a\n%!" pp_state (Stuck stuck) ; assert ( check_error - ~expected_kind:`Decode + ~expected_kind:`No_fallback_decode ~expected_reason:"float instructions are forbidden" stuck) @@ -299,7 +311,7 @@ let test_float64_type () = Format.printf "%a\n%!" pp_state (Stuck stuck) ; assert ( check_error - ~expected_kind:`Decode + ~expected_kind:`No_fallback_decode ~expected_reason:"float instructions are forbidden" stuck) @@ -325,7 +337,7 @@ let test_float_value () = Format.printf "%a\n%!" pp_state (Stuck stuck) ; assert ( check_error - ~expected_kind:`Decode + ~expected_kind:`No_fallback_decode ~expected_reason:"float instructions are forbidden" stuck) diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index 3d76e0ed8bc07e19fadca30dd8d416281d6f8c0b..d041564a45e5852d94823f61dce65e31c7ca49e1 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -794,7 +794,7 @@ let test_unkown_host_function_truncated () = (* The final reason for being stuck should be truncated after [Wasm_pvm_errors.messages_maximum_size]. *) let reason = String.sub reason 0 Wasm_pvm_errors.messages_maximum_size in - assert (is_stuck ~step:`Link ~reason state) ; + assert (is_stuck ~step:`No_fallback_link ~reason state) ; return_unit let test_bulk_noops () = @@ -1073,8 +1073,7 @@ let test_reveal_upgrade_kernel_ok () = let*! () = assert_fallback_kernel tree @@ Some preimage in return_unit -let test_reveal_upgrade_kernel_fallsback_on_error ~binary ~error invalid_kernel - () = +let test_reveal_upgrade_kernel_fallsback_on_error ~binary invalid_kernel () = let open Lwt_result_syntax in let*! modul = wat2wasm @@ reveal_upgrade_kernel () in (* Let's first init the tree to compute. *) @@ -1087,65 +1086,71 @@ let test_reveal_upgrade_kernel_fallsback_on_error ~binary ~error invalid_kernel (* At this point, the kernel should be installed, but not as fallback *) 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_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 - let*! () = assert_fallback_kernel tree @@ Some modul in - (* Check that reveal is requested *) - let*! state_after_first_message = - Wasm.Internal_for_tests.get_tick_state tree - in - assert (not @@ is_stuck state_after_first_message) ; - let*! info = Wasm.get_info tree in - let* () = - let open Wasm_pvm_state in - match info.Wasm_pvm_state.input_request with - | Wasm_pvm_state.Reveal_required (Reveal_raw_data hash) -> - (* The PVM has reached a point where it’s asking for some - preimage. Since the memory is left blank, we are looking - for the zero hash *) - let zero_hash = String.make 33 '\000' in - assert (hash = zero_hash) ; - return_unit - | No_input_required | Input_required | Reveal_required _ -> assert false - in let*! preimage = if binary then Lwt.return invalid_kernel else wat2wasm invalid_kernel in - let*! tree = Wasm.reveal_step (Bytes.of_string preimage) tree in - let*! tree = eval_until_input_requested tree in - let*! state_after_reveal = Wasm.Internal_for_tests.get_tick_state tree in - assert (not @@ is_stuck state_after_reveal) ; - (* At this point, the new_kernel should be installed, the old as fallback *) - let*! () = assert_kernel tree @@ Some preimage in - let*! () = assert_fallback_kernel tree @@ Some modul in - (* run until proper input requested *) - let*! tree = eval_until_input_requested tree in - let*! state_after_reveal = Wasm.Internal_for_tests.get_tick_state tree in - assert (not @@ is_stuck state_after_reveal) ; - (* At this point, the new_kernel should be installed, including as fallback *) - let*! () = assert_kernel tree @@ Some preimage in - let*! () = assert_fallback_kernel tree @@ Some modul in + (* Run the kernel, it should request a preimage reveal *) + let run_installer tree level = + let*! tree = set_empty_inbox_step level 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 + let*! () = assert_fallback_kernel tree @@ Some modul in + (* Check that reveal is requested *) + let*! state_after_first_message = + Wasm.Internal_for_tests.get_tick_state tree + in + assert (not @@ is_stuck state_after_first_message) ; + let*! info = Wasm.get_info tree in + let* () = + let open Wasm_pvm_state in + match info.Wasm_pvm_state.input_request with + | Wasm_pvm_state.Reveal_required (Reveal_raw_data hash) -> + (* The PVM has reached a point where it’s asking for some + preimage. Since the memory is left blank, we are looking + for the zero hash *) + let zero_hash = String.make 33 '\000' in + assert (hash = zero_hash) ; + return_unit + | No_input_required | Input_required | Reveal_required _ -> assert false + in + let*! tree = Wasm.reveal_step (Bytes.of_string preimage) tree in + let*! state_after_reveal = Wasm.Internal_for_tests.get_tick_state tree in + assert (not @@ is_stuck state_after_reveal) ; + let*! tree = eval_until_input_requested tree in + let*! state_after_reveal = Wasm.Internal_for_tests.get_tick_state tree in + assert (not @@ is_stuck state_after_reveal) ; + (* At this point, the new_kernel should be installed, including as fallback *) + let*! () = assert_kernel tree @@ Some preimage in + let*! () = assert_fallback_kernel tree @@ Some modul in + Lwt.return_ok tree + in + let* tree = run_installer tree 0l in (* run with new input, this should be the new kernel *) 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 in - (* The pvm should be temporarily stuck *) - assert (is_stuck ~step:error state_after_second_message) ; - (* The kernel is set to the invalid one *) - let*! () = assert_kernel tree @@ Some preimage in - let*! () = assert_fallback_kernel tree @@ Some modul in - (* Running the kernel one more tick should result in fallback *) - let*! tree = Wasm.compute_step tree in - let*! post_stuck_state = Wasm.Internal_for_tests.get_tick_state tree in - assert (not @@ is_stuck post_stuck_state) ; - (* The kernel is set to the invalid one *) + let has_upgrade_error_flag tree = + let*! durable = wrap_as_durable_storage tree in + let durable = Durable.of_storage_exn durable in + let*! found_error = + Durable.find_value durable Constants.upgrade_error_flag_key + in + Lwt.return_ok @@ Option.is_some found_error + in + (* The pvm should have an upgrade error, but not be stuck *) + assert (not @@ is_stuck state_after_second_message) ; + let* has_upgrade_error = has_upgrade_error_flag tree in + assert has_upgrade_error ; + (* The kernel is set to the fallback one *) let*! () = assert_kernel tree @@ Some modul in let*! () = assert_fallback_kernel tree @@ Some modul in + (* The next evaluation should delete the upgrade error. *) + let* tree = run_installer tree 3l in + let* has_upgrade_error = has_upgrade_error_flag tree in + assert (not has_upgrade_error) ; return_unit let test_kernel_reboot_gen ~reboots ~expected_reboots ~pvm_max_reboots = @@ -1637,14 +1642,12 @@ let tests = `Quick (test_reveal_upgrade_kernel_fallsback_on_error ~binary:true - ~error:`Decode "INVALID WASM!!!"); tztest "Test kernel upgrade fallsback on linking error" `Quick (test_reveal_upgrade_kernel_fallsback_on_error ~binary:false - ~error:`Link {| (module (import "invalid_module" "write_debug" @@ -1655,7 +1658,6 @@ let tests = `Quick (test_reveal_upgrade_kernel_fallsback_on_error ~binary:false - ~error:`Init "(module (memory 1))"); tztest "Test scheduling with 10 inputs in a unique inbox" diff --git a/src/lib_scoru_wasm/wasm_vm.ml b/src/lib_scoru_wasm/wasm_vm.ml index b173d04c89eeec664340f3d8fd5d5b7a69a1bebe..bb97ee53478244d5c751f24ef0d3bb668f4e9de4 100644 --- a/src/lib_scoru_wasm/wasm_vm.ml +++ b/src/lib_scoru_wasm/wasm_vm.ml @@ -58,6 +58,29 @@ let has_stuck_flag durable = let+ stuck = Durable.(find_value durable Constants.stuck_flag_key) in Option.is_some stuck +let has_upgrade_error_flag durable = + let open Lwt_syntax in + let+ error = Durable.(find_value durable Constants.upgrade_error_flag_key) in + Option.is_some error + +let patch_flags_on_eval_successful durable = + let open Lwt_syntax in + (* 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 + let* durable = + if has_stuck_flag then + Durable.(delete ~edit_readonly:true durable Constants.stuck_flag_key) + else Lwt.return durable + in + let* has_upgrade_error_flag = has_upgrade_error_flag durable in + let+ durable = + if has_upgrade_error_flag then + Durable.( + delete ~edit_readonly:true durable Constants.upgrade_error_flag_key) + else Lwt.return durable + in + durable + let mark_for_reboot {reboot_counter; durable; _} = let open Lwt_syntax in let+ has_reboot_flag = has_reboot_flag durable in @@ -114,6 +137,14 @@ let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = Constants.kernel_fallback_key Constants.kernel_key in + let* durable = + Durable.write_value_exn + ~edit_readonly:true + durable + Constants.upgrade_error_flag_key + 0L + "" + in return ~durable Padding else return ~status:Failing (Stuck (No_fallback_kernel cause)) | Stuck e -> return ~status:Failing (Stuck e) @@ -248,11 +279,8 @@ let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = return ~durable 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 + let* durable = patch_flags_on_eval_successful durable in + return ~durable Padding | Eval {config; module_reg} -> (* Continue execution. *) let store = Durable.to_storage durable in @@ -386,6 +414,10 @@ let compute_step pvm_state = let input_request pvm_state = match pvm_state.tick_state with + | Stuck (Decode_error _ | Init_error _ | Link_error _) -> + (* These stuck states are recovered on the next tick by + the fallback mechanism. *) + Wasm_pvm_state.No_input_required | Stuck _ -> Wasm_pvm_state.Input_required | Snapshot -> Wasm_pvm_state.No_input_required | Collect -> Wasm_pvm_state.Input_required @@ -396,7 +428,9 @@ let input_request pvm_state = | _ -> 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) + match pvm_state.tick_state with + | Padding -> not @@ is_time_for_snapshot pvm_state + | _ -> false let measure_executed_ticks (transition : pvm_state -> pvm_state Lwt.t) (initial_state : pvm_state) : (pvm_state * int64) Lwt.t = diff --git a/src/lib_scoru_wasm/wasm_vm.mli b/src/lib_scoru_wasm/wasm_vm.mli index b06de110f95b546120f25ded4d8ca30c132b9f03..d5a482a2625715e8402b38b4797006c6f5374e56 100644 --- a/src/lib_scoru_wasm/wasm_vm.mli +++ b/src/lib_scoru_wasm/wasm_vm.mli @@ -49,6 +49,11 @@ val compute_step_many_until : finished successfully. *) val eval_has_finished : tick_state -> bool +(** [patch_flags_on_eval_successful durable] clears flags set by + previous attempted runs of kernel_run. Once an evaluation has + succeeded, these can be safely deleted. *) +val patch_flags_on_eval_successful : Durable.t -> Durable.t Lwt.t + (** [should_compute pvm_state] probes whether it is possible to continue with more computational steps. *) val should_compute : pvm_state -> bool