From 9c8725ed9bd7a8a80b4ad4a7a3d98347eda84de8 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Wed, 5 Oct 2022 19:32:34 +0200 Subject: [PATCH 1/4] WASM/PVM: allows reboot during the same input tick --- src/lib_scoru_wasm/test/test_wasm_pvm.ml | 1 + src/lib_scoru_wasm/wasm_pvm.ml | 127 ++++++++++++++++++----- src/lib_scoru_wasm/wasm_pvm.mli | 3 + src/lib_scoru_wasm/wasm_pvm_sig.ml | 2 + 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index 6d3981278ccd..ecb3c61d92b4 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -246,6 +246,7 @@ let build_snapshot_wasm_state_from_set_input snapshot_tick tree in + let* tree = Wasm.Internal_for_tests.reset_reboot_counter tree in Test_encodings_util.Tree_encoding_runner.encode (Tezos_tree_encoding.value ["pvm"; "last_top_level_call"] Data_encoding.n) snapshot_tick diff --git a/src/lib_scoru_wasm/wasm_pvm.ml b/src/lib_scoru_wasm/wasm_pvm.ml index 6c81baa35b8a..c04d2deaf4ef 100644 --- a/src/lib_scoru_wasm/wasm_pvm.ml +++ b/src/lib_scoru_wasm/wasm_pvm.ml @@ -31,12 +31,21 @@ let wasm_main_module_name = "main" kernel to expose a function named [kernel_next]. *) let wasm_entrypoint = "kernel_next" -(* TODO: #3590 +(* TODO: https://gitlab.com/tezos/tezos/-/issues/3590 An appropriate number should be used, currently 100 times the nb of ticks it takes tx_kernel to init, deposit, then withdraw (so 100x 2 billion ticks) *) let wasm_max_tick = Z.of_int 200_000_000_000 +(* 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 + +(* Flag used in the durable storage by the kernel to ask a reboot from the PVM + without consuming an input. *) +let reboot_flag_key = Durable.key_of_string_exn "/kernel/env/reboot" + module Wasm = Tezos_webassembly_interpreter type tick_state = @@ -56,12 +65,13 @@ type tick_state = | Stuck of Wasm_pvm_errors.t | Snapshot -type computation_status = Starting | Restarting | Running | Failing +type computation_status = Starting | Restarting | Running | Failing | Reboot type pvm_state = { last_input_info : Wasm_pvm_sig.input_info option; (** Info about last read input. *) current_tick : Z.t; (** Current tick of the PVM. *) + reboot_counter : Z.t; (** Number of reboots for the current input. *) durable : Durable.t; (** The durable storage of the PVM. *) buffers : Wasm.Eval.buffers; (** Input and outut buffers used by the PVM host functions. *) @@ -159,6 +169,7 @@ struct conv (fun ( last_input_info, current_tick, + reboot_counter, durable, buffers, tick_state, @@ -167,6 +178,7 @@ struct { last_input_info; current_tick; + reboot_counter; durable; buffers = (*`Gather_floppies` uses `get_info`, that decodes the state of the @@ -181,6 +193,7 @@ struct (fun { last_input_info; current_tick; + reboot_counter; durable; buffers; tick_state; @@ -189,15 +202,20 @@ struct } -> ( last_input_info, current_tick, + reboot_counter, durable, Some buffers, tick_state, last_top_level_call, max_nb_ticks )) - (tup7 + (tup8 ~flatten:true (value_option ["wasm"; "input"] Wasm_pvm_sig.input_info_encoding) (value ~default:Z.zero ["wasm"; "current_tick"] Data_encoding.n) + (value + ~default:kernel_next_maximum_reboot + ["wasm"; "reboot_counter"] + Data_encoding.n) (scope ["durable"] Durable.encoding) (option durable_buffers_encoding) (scope ["wasm"] tick_state_encoding) @@ -228,6 +246,27 @@ struct let is_time_for_snapshot pvm_state = Z.Compare.(ticks_to_snapshot pvm_state <= Z.zero) + let has_reboot_flag durable = + let open Lwt_syntax in + let+ allows_reboot = + Lwt.catch + (fun () -> Durable.(find_value durable reboot_flag_key)) + (function exn -> raise exn) + in + allows_reboot <> None + + 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 initial_boot_state () = + Decode + (Tezos_webassembly_interpreter.Decode.initial_decode_kont + ~name:wasm_main_module_name) + let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = let open Lwt_syntax in @@ -237,12 +276,29 @@ struct match tick_state with | Stuck e -> return ~status:Failing (Stuck e) | Snapshot -> - return - ~status:Failing - (Stuck (Wasm_pvm_errors.invalid_state "snapshot is a tick state")) + let* has_reboot_flag = has_reboot_flag durable in + if has_reboot_flag then + let* durable = Durable.(delete durable reboot_flag_key) in + return ~durable (initial_boot_state ()) + else + return + ~status:Failing + (Stuck + (Wasm_pvm_errors.invalid_state + "snapshot is an input tick state")) | _ when eval_has_finished tick_state && is_time_for_snapshot pvm_state -> - (* We have an empty set of admin instructions *) - return ~status:Restarting Snapshot + (* We have an empty set of admin instructions, we can either reboot + without consuming another input if the flag has been set or read a + new input. *) + let* status = mark_for_reboot pvm_state.reboot_counter durable in + (* Execution took too many reboot *) + if status = Failing then + return + ~status + (Stuck + (Wasm_pvm_errors.invalid_state + "Kernel attempted too many reboots within the same input")) + else return ~status Snapshot | _ when is_time_for_snapshot pvm_state -> (* Execution took too many ticks *) return ~status:Failing (Stuck Too_many_ticks) @@ -380,28 +436,47 @@ struct let next_last_top_level_call {current_tick; last_top_level_call; _} = function - | Restarting -> Z.succ current_tick + | Restarting | Reboot -> Z.succ current_tick | Starting | Failing | Running -> last_top_level_call + let next_reboot_counter {reboot_counter; _} status = + match status with + | Reboot -> Z.pred reboot_counter + | Restarting | Failing -> maximum_reboots_per_input + | Starting | Running -> reboot_counter + let compute_step_inner pvm_state = let open Lwt_syntax in (* Calculate the next tick state. *) let* durable, tick_state, status = next_tick_state pvm_state in let current_tick = Z.succ pvm_state.current_tick in let last_top_level_call = next_last_top_level_call pvm_state status in + let reboot_counter = next_reboot_counter pvm_state status in let pvm_state = - {pvm_state with tick_state; durable; current_tick; last_top_level_call} + { + pvm_state with + tick_state; + durable; + current_tick; + last_top_level_call; + reboot_counter; + } in return pvm_state let input_request pvm_state = + let open Lwt_syntax in match pvm_state.tick_state with - | Stuck _ | Snapshot -> Wasm_pvm_sig.Input_required + | Stuck _ -> return Wasm_pvm_sig.Input_required + | Snapshot -> + let+ has_reboot_flag = has_reboot_flag pvm_state.durable in + if has_reboot_flag then Wasm_pvm_sig.No_input_required + else Wasm_pvm_sig.Input_required | Eval config -> ( match Tezos_webassembly_interpreter.Eval.is_reveal_tick config with - | Some reveal -> Reveal_required reveal - | None -> No_input_required) - | _ -> No_input_required + | Some reveal -> return (Wasm_pvm_sig.Reveal_required reveal) + | None -> return Wasm_pvm_sig.No_input_required) + | _ -> return Wasm_pvm_sig.No_input_required let is_top_level_padding pvm_state = eval_has_finished pvm_state.tick_state @@ -412,7 +487,8 @@ struct assert (max_steps > 0L) ; let rec go steps_left pvm_state = - if steps_left > 0L && input_request pvm_state = No_input_required then + let* input_request = input_request pvm_state in + if steps_left > 0L && input_request = No_input_required then if is_top_level_padding pvm_state then (* We're in the top-level padding after the evaluation has finished. That means we can skip up to the tick before the @@ -473,15 +549,12 @@ struct let get_info tree = let open Lwt_syntax in - let+ ({current_tick; last_input_info; _} as pvm) = + let* ({current_tick; last_input_info; _} as pvm) = Tree_encoding_runner.decode pvm_state_encoding tree in + let+ input_request = input_request pvm in Wasm_pvm_sig. - { - current_tick; - last_input_read = last_input_info; - input_request = input_request pvm; - } + {current_tick; last_input_read = last_input_info; input_request} let set_input_step input_info message tree = let open Lwt_syntax in @@ -510,9 +583,7 @@ struct (* TODO: https://gitlab.com/tezos/tezos/-/issues/3157 The goal is to read a complete inbox. *) (* Go back to decoding *) - Decode - (Tezos_webassembly_interpreter.Decode.initial_decode_kont - ~name:wasm_main_module_name) + initial_boot_state () | Decode _ -> Lwt.return (Stuck @@ -621,6 +692,14 @@ struct let* pvm_state = Tree_encoding_runner.decode pvm_state_encoding tree in let pvm_state = {pvm_state with max_nb_ticks = n} in Tree_encoding_runner.encode pvm_state_encoding pvm_state tree + + let reset_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 = pvm_state.maximum_reboots_per_input} + in + Tree_encoding_runner.encode pvm_state_encoding pvm_state tree end end diff --git a/src/lib_scoru_wasm/wasm_pvm.mli b/src/lib_scoru_wasm/wasm_pvm.mli index 98aefbe4a04e..ff96bfbd5ac0 100644 --- a/src/lib_scoru_wasm/wasm_pvm.mli +++ b/src/lib_scoru_wasm/wasm_pvm.mli @@ -23,6 +23,9 @@ (* *) (*****************************************************************************) +(** Maximum number of reboots per inputs. *) +val maximum_reboots_per_input : Z.t + type tick_state = | Decode of Tezos_webassembly_interpreter.Decode.decode_kont | Link of { diff --git a/src/lib_scoru_wasm/wasm_pvm_sig.ml b/src/lib_scoru_wasm/wasm_pvm_sig.ml index c9b29b7b604b..2d2883848d1d 100644 --- a/src/lib_scoru_wasm/wasm_pvm_sig.ml +++ b/src/lib_scoru_wasm/wasm_pvm_sig.ml @@ -74,6 +74,8 @@ module type Internal_for_tests = sig val is_stuck : tree -> Wasm_pvm_errors.t option Lwt.t val set_max_nb_ticks : Z.t -> tree -> tree Lwt.t + + val reset_reboot_counter : tree -> tree Lwt.t end (** This module type defines a WASM VM API used for smart-contract rollups. *) -- GitLab From 78526d56f4aa715be14afd7d9ee64187cd2c015a Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 6 Oct 2022 16:03:59 +0200 Subject: [PATCH 2/4] WASM/PVM: add Too_many_reboots error --- src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml | 2 ++ src/lib_scoru_wasm/test/wasm_utils.ml | 1 + src/lib_scoru_wasm/wasm_pvm.ml | 7 +------ src/lib_scoru_wasm/wasm_pvm_errors.ml | 7 +++++++ src/lib_scoru_wasm/wasm_pvm_errors.mli | 2 ++ 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml b/src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml index 87503cc6ac6c..a7b73ee5eef8 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm_encodings.ml @@ -274,6 +274,8 @@ let pp_error_state out = function Format.fprintf out "@[Unknown_error (%s)@]" err | Wasm_pvm_errors.Too_many_ticks -> Format.fprintf out "@[Too_many_ticks@]" + | Wasm_pvm_errors.Too_many_reboots -> + Format.fprintf out "@[Too_many_reboots@]" let print_error_state = Format.asprintf "%a" pp_error_state diff --git a/src/lib_scoru_wasm/test/wasm_utils.ml b/src/lib_scoru_wasm/test/wasm_utils.ml index 48cd70a1d0e1..22c12513f9b3 100644 --- a/src/lib_scoru_wasm/test/wasm_utils.ml +++ b/src/lib_scoru_wasm/test/wasm_utils.ml @@ -143,6 +143,7 @@ let check_error ?expected_kind ?expected_reason error = not safe to rely on its string representation. *) | Some `Unknown, Unknown_error _ -> true | Some `Too_many_ticks, Too_many_ticks -> true + | Some `Too_many_reboots, Too_many_reboots -> true (* The expected step doesn't corresponds to the actual stuck step. *) | Some _, _ -> false (* No check to do, we simply assume the PVM is in a stuck state. *) diff --git a/src/lib_scoru_wasm/wasm_pvm.ml b/src/lib_scoru_wasm/wasm_pvm.ml index c04d2deaf4ef..83296168c308 100644 --- a/src/lib_scoru_wasm/wasm_pvm.ml +++ b/src/lib_scoru_wasm/wasm_pvm.ml @@ -292,12 +292,7 @@ struct new input. *) let* status = mark_for_reboot pvm_state.reboot_counter durable in (* Execution took too many reboot *) - if status = Failing then - return - ~status - (Stuck - (Wasm_pvm_errors.invalid_state - "Kernel attempted too many reboots within the same input")) + if status = Failing then return ~status (Stuck Too_many_reboots) else return ~status Snapshot | _ when is_time_for_snapshot pvm_state -> (* Execution took too many ticks *) diff --git a/src/lib_scoru_wasm/wasm_pvm_errors.ml b/src/lib_scoru_wasm/wasm_pvm_errors.ml index 04a6faa0c451..df063a5d8e3e 100644 --- a/src/lib_scoru_wasm/wasm_pvm_errors.ml +++ b/src/lib_scoru_wasm/wasm_pvm_errors.ml @@ -47,6 +47,7 @@ type t = | Invalid_state of truncated_string | Unknown_error of truncated_string | Too_many_ticks + | Too_many_reboots let decode_state_to_string_raw = function | Decode.Byte_vector_step -> "Byte_vector_step" @@ -212,6 +213,12 @@ let encoding = (constant "too_many_ticks") (function Too_many_ticks -> Some () | _ -> None) (fun () -> Too_many_ticks); + case + (Tag 7) + ~title:"Too_many_reboots" + (constant "too_many_reboots") + (function Too_many_reboots -> Some () | _ -> None) + (fun () -> Too_many_reboots); ] let link_error kind ~module_name ~item_name = diff --git a/src/lib_scoru_wasm/wasm_pvm_errors.mli b/src/lib_scoru_wasm/wasm_pvm_errors.mli index e6a9b98aa438..103c47b2390b 100644 --- a/src/lib_scoru_wasm/wasm_pvm_errors.mli +++ b/src/lib_scoru_wasm/wasm_pvm_errors.mli @@ -67,6 +67,8 @@ type t = (** Wraps unexpected exceptions raised by the interpreter. *) | Too_many_ticks (** The maximum number of ticks was reached before the end of current top level call *) + | Too_many_reboots + (** The maximum number of reboots was reached before the next inputs *) (* [invalid_state msg] builds an `Invalid_state` error out of a message. *) val invalid_state : string -> t -- GitLab From 6050f3c97607674dfa3ce01dc631dc72002ad3e0 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Wed, 5 Oct 2022 19:33:47 +0200 Subject: [PATCH 3/4] WASM/Test: test rebooting kernel --- src/lib_scoru_wasm/test/test_wasm_pvm.ml | 191 +++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index ecb3c61d92b4..9c83abf15533 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -545,6 +545,192 @@ let test_durable_store_io () = assert (expected = value) ; return_unit +let test_kernel_reboot_gen max_reboot expected_reboots = + let open Lwt_result_syntax in + (* Extracted from the kernel, these are the constant values used to build the + initial memory and the addresses where values are stored. *) + let data_offset_start = 100 in + + (* Durable storage: reboot key *) + let reboot_key = "/kernel/env/reboot" in + let reboot_key_length = String.length reboot_key in + let reboot_key_offset = data_offset_start in + + (* Durable storage: reboot counter *) + let reboot_counter_key = "/reboot/counter" in + let reboot_counter_key_length = String.length reboot_counter_key in + let reboot_counter_key_offset = reboot_key_offset + reboot_key_length in + + (* Memory only: reboot flag and reboot counter *) + let reboot_flag_in_memory_offset = + reboot_counter_key_offset + reboot_counter_key_offset + in + let reboot_counter_in_memory_offset = + reboot_flag_in_memory_offset + + Tezos_webassembly_interpreter.Types.(num_size I32Type) + (* Number of bytes taken by the flag in memory *) + in + + let reboot_module = + Format.sprintf + {| +(module + (import "rollup_safe_core" "store_write" + (func $store_write (param i32 i32 i32 i32 i32) (result i32))) + (import "rollup_safe_core" "store_read" + (func $store_read (param i32 i32 i32 i32 i32) (result i32))) + (import "rollup_safe_core" "store_has" + (func $store_has (param i32 i32) (result i32))) + ;; Durable keys + (data (i32.const %d) "%s") + (data (i32.const %d) "%s") + + (memory 1) + (export "mem" (memory 0)) + + ;; Finds the reboot counter in the durable storage and increase it, initializes + ;; it otherwise. + (func $incr_reboot_counter + (param) (result i32) + + (local $reboot_counter_key i32) + (local $reboot_counter_key_length i32) + (local $reboot_counter_offset i32) + (local $reboot_counter_value i32) + + (local.set $reboot_counter_key (i32.const %d)) + (local.set $reboot_counter_key_length (i32.const %d)) + (local.set $reboot_counter_offset (i32.const %d)) + (local.set $reboot_counter_value (i32.const 0)) ;; initial value of the counter + + ;; First check the counter exists in the durable storage + (call $store_has + (local.get $reboot_counter_key) + (local.get $reboot_counter_key_length)) + ;; if store_has returns `1`, the key exists with a value without + ;; subtrees, that's exactly what we expect. + (i32.const 1) + (i32.eq) + + (if + ;; if it exists, load it and increment it into $reboot_counter_value + (then + (call $store_read + (local.get $reboot_counter_key) + (local.get $reboot_counter_key_length) + (i32.const 0) ;; value at offset 0 in the durable storage + (local.get $reboot_counter_offset) + (i32.const 4)) ;; we expect at most 4 bytes, since the counter is + ;; an i32 in V128 encoding) + (drop) ;; we drop the number of bytes read + (local.set $reboot_counter_value + (i32.add (i32.load (local.get $reboot_counter_offset)) + (i32.const 1))) + ) + ) + (i32.store (local.get $reboot_counter_offset) + (local.get $reboot_counter_value)) + (call $store_write + (local.get $reboot_counter_key) + (local.get $reboot_counter_key_length) + (i32.const 0) ;; value at offset 0 in the durable storage + (local.get $reboot_counter_offset) + (i32.const 4)) ;; we expect at most 4 bytes, since the counter is + ;; an i32 in V128 encoding + (drop) + (local.get $reboot_counter_value) + ) + + (func (export "kernel_next") + (local $reboot_flag_key i32) + (local $reboot_flag_length i32) + (local $reboot_flag_value_offset i32) + (local $reboot_flag_value i32) + (local $reboot_max i32) + + (local.set $reboot_flag_key (i32.const %d)) + (local.set $reboot_flag_length (i32.const %d)) + (local.set $reboot_flag_value_offset (i32.const %d)) + (local.set $reboot_max (i32.const %ld)) + + ;; first increase the reboot counter; and put the counter on the stack + (call $incr_reboot_counter) + ;; determine the reboot flag: if it is lesser than the maximum then + ;; reboot, otherwise don't reboot + (local.get $reboot_max) + (i32.lt_s) + ;; set the reboot flag + (if + (then + (call $store_write + (local.get $reboot_flag_key) + (local.get $reboot_flag_length) + (i32.const 0) + (local.get $reboot_flag_value) + (i32.const 4));; we expect at most 4 bytes, since the counter is + ;; an i32 in V128 encoding + (drop)) + ) + + ;; explicitely do nothing + (nop) + ) + ) +|} + (* Data section *) + reboot_key_offset + reboot_key + reboot_counter_key_offset + reboot_counter_key + (* `incr_counter` function *) + reboot_counter_key_offset + reboot_counter_key_length + reboot_counter_in_memory_offset + (* `kernel_next` function *) + data_offset_start (* == reboot_key's offset *) + reboot_key_length + reboot_flag_in_memory_offset + max_reboot + in + (* Let's first init the tree to compute. *) + let*! tree = initial_tree ~from_binary:false reboot_module in + let*! tree = eval_until_input_requested tree in + let*! tree = set_input_step "dummy_input" 0 tree in + let*! tree = eval_until_input_requested tree in + let*! state = Wasm.Internal_for_tests.get_tick_state tree 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`. *) + if max_reboot <= expected_reboots then assert (not @@ is_stuck state) + else assert (is_stuck ~step:`Too_many_reboots state) ; + + 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 + match value with + | None -> + failwith + "Evaluation error: couldn't find the reboot counter in the durable \ + storage" + | Some value -> + let*! value = Tezos_lazy_containers.Chunked_byte_vector.to_bytes value in + (* WASM Values in memories are encoded in little-endian order. *) + let value = Bytes.get_int32_le value 0 in + assert (value = expected_reboots) ; + return_unit + +let test_kernel_reboot () = + (* The kernel doesn't accept more than 10 reboots between two inputs, this test will succeed. *) + test_kernel_reboot_gen 5l 5l + +let test_kernel_reboot_failing () = + (* The kernel doesn't accept more than 10 reboots between two inputs, it will + then fail after 10. *) + test_kernel_reboot_gen 15l 10l + let tests = [ tztest @@ -598,4 +784,9 @@ let tests = test_invalid_key_truncated; tztest "Test bulk no-ops function properly" `Quick test_bulk_noops; tztest "Test durable store io" `Quick test_durable_store_io; + tztest "Test reboot" `Quick test_kernel_reboot; + tztest + "Test reboot takes too many reboots" + `Quick + test_kernel_reboot_failing; ] -- GitLab From 17075398732cbd93f5b768b566eb10e68d6e528c Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 10 Oct 2022 16:39:17 +0200 Subject: [PATCH 4/4] WASM/PVM: make number of reboots configurable for tests This will make it easily available in the durable storage once read only keys are implemented. --- src/lib_scoru_wasm/test/test_wasm_pvm.ml | 17 +++++++----- src/lib_scoru_wasm/test/wasm_utils.ml | 7 +++-- src/lib_scoru_wasm/wasm_pvm.ml | 34 +++++++++++++++++------- src/lib_scoru_wasm/wasm_pvm_sig.ml | 2 ++ 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index 9c83abf15533..1443666ca700 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -545,7 +545,7 @@ let test_durable_store_io () = assert (expected = value) ; return_unit -let test_kernel_reboot_gen max_reboot expected_reboots = +let test_kernel_reboot_gen ~reboots ~expected_reboots ~pvm_max_reboots = let open Lwt_result_syntax in (* Extracted from the kernel, these are the constant values used to build the initial memory and the addresses where values are stored. *) @@ -690,10 +690,15 @@ let test_kernel_reboot_gen max_reboot expected_reboots = data_offset_start (* == reboot_key's offset *) reboot_key_length reboot_flag_in_memory_offset - max_reboot + reboots in (* Let's first init the tree to compute. *) - let*! tree = initial_tree ~from_binary:false reboot_module in + let*! tree = + initial_tree + ~max_reboots:(Z.of_int32 pvm_max_reboots) + ~from_binary:false + reboot_module + in let*! tree = eval_until_input_requested tree in let*! tree = set_input_step "dummy_input" 0 tree in let*! tree = eval_until_input_requested tree in @@ -702,7 +707,7 @@ let test_kernel_reboot_gen max_reboot expected_reboots = (* 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`. *) - if max_reboot <= expected_reboots then assert (not @@ is_stuck state) + if reboots <= pvm_max_reboots then assert (not @@ is_stuck state) else assert (is_stuck ~step:`Too_many_reboots state) ; let*! durable = wrap_as_durable_storage tree in @@ -724,12 +729,12 @@ let test_kernel_reboot_gen max_reboot expected_reboots = let test_kernel_reboot () = (* The kernel doesn't accept more than 10 reboots between two inputs, this test will succeed. *) - test_kernel_reboot_gen 5l 5l + test_kernel_reboot_gen ~reboots:5l ~expected_reboots:5l ~pvm_max_reboots:10l let test_kernel_reboot_failing () = (* The kernel doesn't accept more than 10 reboots between two inputs, it will then fail after 10. *) - test_kernel_reboot_gen 15l 10l + test_kernel_reboot_gen ~reboots:15l ~expected_reboots:10l ~pvm_max_reboots:10l let tests = [ diff --git a/src/lib_scoru_wasm/test/wasm_utils.ml b/src/lib_scoru_wasm/test/wasm_utils.ml index 22c12513f9b3..52f002e1e082 100644 --- a/src/lib_scoru_wasm/test/wasm_utils.ml +++ b/src/lib_scoru_wasm/test/wasm_utils.ml @@ -41,7 +41,9 @@ let wat2wasm code = let default_max_tick = 100000L -let initial_tree ?(max_tick = default_max_tick) ?(from_binary = false) code = +let initial_tree ?(max_tick = default_max_tick) + ?(max_reboots = Wasm_pvm.maximum_reboots_per_input) ?(from_binary = false) + code = let open Lwt.Syntax in let max_tick_Z = Z.of_int64 max_tick in let* empty_tree = empty_tree () in @@ -56,7 +58,8 @@ let initial_tree ?(max_tick = default_max_tick) ?(from_binary = false) code = ~empty_tree boot_sector in - Wasm.Internal_for_tests.set_max_nb_ticks max_tick_Z tree + let* tree = Wasm.Internal_for_tests.set_max_nb_ticks max_tick_Z tree in + Wasm.Internal_for_tests.set_maximum_reboots_per_input max_reboots tree let eval_until_stuck ?(max_steps = 20000L) tree = let open Lwt.Syntax in diff --git a/src/lib_scoru_wasm/wasm_pvm.ml b/src/lib_scoru_wasm/wasm_pvm.ml index 83296168c308..fcf9b3d8f5d0 100644 --- a/src/lib_scoru_wasm/wasm_pvm.ml +++ b/src/lib_scoru_wasm/wasm_pvm.ml @@ -79,6 +79,7 @@ type pvm_state = { last_top_level_call : Z.t; (** Last tick corresponding to a top-level call. *) max_nb_ticks : Z.t; (** Number of ticks between top level call. *) + maximum_reboots_per_input : Z.t; (** Number of reboots between two inputs. *) } module Make (T : Tezos_tree_encoding.TREE) : @@ -174,11 +175,13 @@ struct buffers, tick_state, last_top_level_call, - max_nb_ticks ) -> + max_nb_ticks, + maximum_reboots_per_input ) -> { last_input_info; current_tick; - reboot_counter; + reboot_counter = + Option.value ~default:maximum_reboots_per_input reboot_counter; durable; buffers = (*`Gather_floppies` uses `get_info`, that decodes the state of the @@ -189,6 +192,7 @@ struct tick_state; last_top_level_call; max_nb_ticks; + maximum_reboots_per_input; }) (fun { last_input_info; @@ -199,23 +203,22 @@ struct tick_state; last_top_level_call; max_nb_ticks; + maximum_reboots_per_input; } -> ( last_input_info, current_tick, - reboot_counter, + Some reboot_counter, durable, Some buffers, tick_state, last_top_level_call, - max_nb_ticks )) - (tup8 + max_nb_ticks, + maximum_reboots_per_input )) + (tup9 ~flatten:true (value_option ["wasm"; "input"] Wasm_pvm_sig.input_info_encoding) (value ~default:Z.zero ["wasm"; "current_tick"] Data_encoding.n) - (value - ~default:kernel_next_maximum_reboot - ["wasm"; "reboot_counter"] - Data_encoding.n) + (value_option ["wasm"; "reboot_counter"] Data_encoding.n) (scope ["durable"] Durable.encoding) (option durable_buffers_encoding) (scope ["wasm"] tick_state_encoding) @@ -226,6 +229,10 @@ struct (value ~default:wasm_max_tick ["pvm"; "max_nb_ticks"] + Data_encoding.n) + (value + ~default:maximum_reboots_per_input + ["pvm"; "maximum_reboots_per_input"] Data_encoding.n)) let kernel_key = Durable.key_of_string_exn "/kernel/boot.wasm" @@ -434,7 +441,8 @@ struct | Restarting | Reboot -> Z.succ current_tick | Starting | Failing | Running -> last_top_level_call - let next_reboot_counter {reboot_counter; _} status = + 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 @@ -688,6 +696,12 @@ struct let pvm_state = {pvm_state with max_nb_ticks = n} in Tree_encoding_runner.encode pvm_state_encoding pvm_state tree + let set_maximum_reboots_per_input n tree = + let open Lwt_syntax in + let* pvm_state = Tree_encoding_runner.decode pvm_state_encoding tree in + let pvm_state = {pvm_state with maximum_reboots_per_input = n} 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 diff --git a/src/lib_scoru_wasm/wasm_pvm_sig.ml b/src/lib_scoru_wasm/wasm_pvm_sig.ml index 2d2883848d1d..b4e8daf86e3f 100644 --- a/src/lib_scoru_wasm/wasm_pvm_sig.ml +++ b/src/lib_scoru_wasm/wasm_pvm_sig.ml @@ -75,6 +75,8 @@ module type Internal_for_tests = sig val set_max_nb_ticks : Z.t -> tree -> tree Lwt.t + val set_maximum_reboots_per_input : Z.t -> tree -> tree Lwt.t + val reset_reboot_counter : tree -> tree Lwt.t end -- GitLab