diff --git a/src/lib_scoru_wasm/constants.ml b/src/lib_scoru_wasm/constants.ml index 7ea274a5e0056b4171ab7938bdc10b0b4e37fb96..e32b3d71032b13515dbf930a94c4fc47b259e975 100644 --- a/src/lib_scoru_wasm/constants.ml +++ b/src/lib_scoru_wasm/constants.ml @@ -66,3 +66,7 @@ let reboot_counter_key = Durable.key_of_string_exn "/readonly/kernel/env/reboot_counter" let version_key = Durable.key_of_string_exn "/readonly/wasm_version" + +let stack_size_limit = 300 + +let proto_alpha_name = "alpha_current" diff --git a/src/lib_scoru_wasm/fast/vm.ml b/src/lib_scoru_wasm/fast/vm.ml index 32024cbe4c1473c19c962d6b754fabd09d372375..bf8496680d6b884b47333b8b8c812f1903287991 100644 --- a/src/lib_scoru_wasm/fast/vm.ml +++ b/src/lib_scoru_wasm/fast/vm.ml @@ -153,6 +153,8 @@ let compute_step_many = compute_step_many 0L module Internal_for_tests = struct let compute_step_many_with_hooks = compute_step_many + + let get_wasm_version = Wasm_vm.Internal_for_tests.get_wasm_version end let compute_step_many = compute_step_many ?after_fast_exec:None diff --git a/src/lib_scoru_wasm/helpers/wasm_utils.ml b/src/lib_scoru_wasm/helpers/wasm_utils.ml index 0ee23dfee8eb621fdcbdf5f821de792c0c46e3f9..6ec49d3fc91eec678f9149dc3ff4fa216d0cb813 100644 --- a/src/lib_scoru_wasm/helpers/wasm_utils.ml +++ b/src/lib_scoru_wasm/helpers/wasm_utils.ml @@ -172,7 +172,16 @@ let set_sol_input level tree = in Wasm.set_input_step (input_info level Z.zero) sol_input tree -let set_info_per_level_input level tree = +let set_protocol_migration_input proto level tree = + let sol_input = + Pvm_input_kind.( + Internal_for_tests.to_binary_input + (Internal (Protocol_migration proto)) + None) + in + Wasm.set_input_step (input_info level Z.one) sol_input tree + +let set_info_per_level_input ?(migration_block = false) level tree = let block_hash = Block_hash.zero in let timestamp = Time.Protocol.epoch in let info_res = @@ -189,7 +198,10 @@ let set_info_per_level_input level tree = (Internal Info_per_level) (Some info)) in - Wasm.set_input_step (input_info level Z.one) info_per_level_input tree + Wasm.set_input_step + (input_info level (if migration_block then Z.of_int 2 else Z.one)) + info_per_level_input + tree | Error _ -> (* There's no reason the encoding has failed, but we return the tree anyway *) @@ -215,13 +227,26 @@ let set_eol_input level counter tree = in Wasm.set_input_step (input_info level counter) sol_input tree -let set_inputs_step set_internal_message messages level tree = +let set_inputs_step ?migrate_to set_internal_message 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 = + match migrate_to with + | Some proto -> + let+ tree = set_protocol_migration_input proto level tree in + let (_ : Z.t) = next_message_counter () in + tree + | None -> return tree + in let (_ : Z.t) = next_message_counter () in - let* tree = set_info_per_level_input level tree in + let* tree = + set_info_per_level_input + ~migration_block:(Option.is_some migrate_to) + level + tree + in let* tree = List.fold_left_s (fun tree message -> @@ -231,16 +256,22 @@ let set_inputs_step set_internal_message messages level tree = in set_eol_input level (next_message_counter ()) tree -let set_full_input_step_gen set_internal_message messages level tree = +let set_full_input_step_gen ?migrate_to set_internal_message messages level tree + = let open Lwt_syntax in - let* tree = set_inputs_step set_internal_message messages level tree in + let* tree = + set_inputs_step ?migrate_to set_internal_message messages level tree + in eval_to_snapshot ~max_steps:Int64.max_int tree -let set_full_input_step = set_full_input_step_gen set_internal_message +let set_full_input_step ?migrate_to = + set_full_input_step_gen ?migrate_to set_internal_message -let set_full_raw_input_step = set_full_input_step_gen set_raw_message +let set_full_raw_input_step ?migrate_to = + set_full_input_step_gen set_raw_message ?migrate_to -let set_empty_inbox_step level tree = set_full_input_step [] level tree +let set_empty_inbox_step ?migrate_to level tree = + set_full_input_step ?migrate_to [] level tree let rec eval_until_init tree = let open Lwt_syntax in diff --git a/src/lib_scoru_wasm/pvm_input_kind.ml b/src/lib_scoru_wasm/pvm_input_kind.ml index 09fb67db862a5d10fcf3c45f1e934bd26e858590..c98791b9c3f5d843bf0a525fd3cda345546607ae 100644 --- a/src/lib_scoru_wasm/pvm_input_kind.ml +++ b/src/lib_scoru_wasm/pvm_input_kind.ml @@ -23,6 +23,8 @@ (* *) (*****************************************************************************) +type protocol = Proto_alpha + (* This type mimics [Sc_rollup_inbox_repr.internal_inbox_messages], without fully deserializing the `Transfer`, and is produced by reading the first bytes from the input: @@ -31,12 +33,14 @@ - `\000\001` a start_of_level, - `\000\002` an end_of_level, - `\000\003` an info_per_level input, + - `\000\004` a protocol migration input, - Any other tag will considered as an `Other message`. *) type internal_message_kind = | Transfer | Start_of_level | End_of_level | Info_per_level + | Protocol_migration of protocol (* This type mimics [Sc_rollup_inbox_repr.t] and produced by reading the first bytes from the input: @@ -47,6 +51,15 @@ type internal_message_kind = are not discarded by the PVM, simply not recognized. *) type t = Internal of internal_message_kind | External | Other +let protocol_from_raw payload = + if String.length payload < 3 then None + else + let payload = String.sub payload 2 (String.length payload - 2) in + match Data_encoding.(Binary.of_string_exn string payload) with + | payload when String.equal payload Constants.proto_alpha_name -> + Some (Protocol_migration Proto_alpha) + | _ -> None + let internal_from_raw payload = if String.length payload < 2 then None else @@ -55,6 +68,7 @@ let internal_from_raw payload = | '\001' when String.length payload = 2 -> Some Start_of_level | '\002' when String.length payload = 2 -> Some End_of_level | '\003' -> Some Info_per_level + | '\004' -> protocol_from_raw payload | _ -> None let from_raw_input payload = @@ -70,6 +84,10 @@ let from_raw_input payload = | _ -> Other module Internal_for_tests = struct + let proto_to_binary = function + | Proto_alpha -> + Data_encoding.(Binary.to_string_exn string Constants.proto_alpha_name) + let to_binary_input input message = match (input, message) with | Internal Transfer, Some message -> "\000\000" ^ message @@ -77,6 +95,8 @@ module Internal_for_tests = struct | Internal Start_of_level, None -> "\000\001" | Internal End_of_level, None -> "\000\002" | Internal Info_per_level, Some info -> "\000\003" ^ info + | Internal (Protocol_migration proto), None -> + "\000\004" ^ proto_to_binary proto | Other, _ -> Stdlib.failwith "`Other` messages are impossible cases from the PVM perspective." @@ -86,5 +106,7 @@ module Internal_for_tests = struct | Internal Transfer, None -> Stdlib.failwith "`Transfer` expects a payload" | Internal Info_per_level, None -> Stdlib.failwith "`Info_per_level` expects a payload" + | Internal (Protocol_migration _), Some _ -> + Stdlib.failwith "`Protocol_migration` does not expect 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 index 9ccd2ce348c10c53858e0af0b22838086094da20..780c92ecc878699a4b8fedf65474219b332b5128 100644 --- a/src/lib_scoru_wasm/pvm_input_kind.mli +++ b/src/lib_scoru_wasm/pvm_input_kind.mli @@ -23,6 +23,8 @@ (* *) (*****************************************************************************) +type protocol = Proto_alpha + (** [internal_message_kind] represent an internal message in a inbox. *) type internal_message_kind = | Transfer (* Generic internal message. *) @@ -32,6 +34,7 @@ type internal_message_kind = | Info_per_level (** Internal message containing the timestamp of the current block and the hash of the previous block. *) + | Protocol_migration of protocol (** 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 diff --git a/src/lib_scoru_wasm/test/test_protocol_migration.ml b/src/lib_scoru_wasm/test/test_protocol_migration.ml new file mode 100644 index 0000000000000000000000000000000000000000..bf1e98fe1454d9e8d85640156f2bf2dc8f57db7f --- /dev/null +++ b/src/lib_scoru_wasm/test/test_protocol_migration.ml @@ -0,0 +1,68 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 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. *) +(* *) +(*****************************************************************************) + +(** Testing + ------- + Component: Lib_scoru_wasm protocol migration internal message + Invocation: dune exec src/lib_scoru_wasm/test/test_scoru_wasm.exe \ + -- test "Protocol migration" + Subject: Protocol migration tests for the tezos-scoru-wasm library +*) + +open Tztest +open Wasm_utils + +let noop_module = + {| + (module + (memory 1) + (export "mem"(memory 0)) + (func (export "kernel_run") + nop)) +|} + +let test_protocol_migration_message () = + let open Lwt_syntax in + let* tree = initial_tree ~version:V0 noop_module in + let* tree = eval_until_input_requested tree in + let* version = Wasm.Internal_for_tests.get_wasm_version tree in + assert (version = V0) ; + let* tree = set_empty_inbox_step 0l tree in + let* tree = eval_until_input_requested tree in + let* version = Wasm.Internal_for_tests.get_wasm_version tree in + assert (version = V0) ; + let* tree = set_empty_inbox_step ~migrate_to:Proto_alpha 0l tree in + let* tree = eval_until_input_requested tree in + let* version = Wasm.Internal_for_tests.get_wasm_version tree in + assert (version = V1) ; + Lwt_result_syntax.return_unit + +let tests = + [ + tztest + "protocol migration message handling by the WASM PVM" + `Quick + test_protocol_migration_message; + ] diff --git a/src/lib_scoru_wasm/test/test_scoru_wasm.ml b/src/lib_scoru_wasm/test/test_scoru_wasm.ml index ade5b8be9fd6ede4810d182d440366b300180ea4..6e31adf3909a6fcb112277d01e9aa9ed88b28e38 100644 --- a/src/lib_scoru_wasm/test/test_scoru_wasm.ml +++ b/src/lib_scoru_wasm/test/test_scoru_wasm.ml @@ -51,5 +51,6 @@ let () = ("Debug", Test_debug.tests); ("Host functions ticks", Test_host_functions_ticks.tests); ("Durable snapshot", Test_durable_shapshot.tests); + ("Protocol migration", Test_protocol_migration.tests); ] |> Lwt_main.run diff --git a/src/lib_scoru_wasm/wasm_pvm.ml b/src/lib_scoru_wasm/wasm_pvm.ml index 7c5b0e410ee44ca9b4b5e2495fb09fb69c9df5fc..ed6a5a36f72b41703ad293b268e026edc3858f4a 100644 --- a/src/lib_scoru_wasm/wasm_pvm.ml +++ b/src/lib_scoru_wasm/wasm_pvm.ml @@ -412,6 +412,11 @@ 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 get_wasm_version tree = + let open Lwt.Syntax in + let* pvm = Tree_encoding_runner.decode pvm_state_encoding tree in + Wasm_vm.Internal_for_tests.get_wasm_version pvm + let compute_step_many_with_hooks ?reveal_builtins ?write_debug ?after_fast_exec ?stop_at_snapshot ~max_steps tree = let open Lwt.Syntax in diff --git a/src/lib_scoru_wasm/wasm_vm.ml b/src/lib_scoru_wasm/wasm_vm.ml index 6aa06e23ab452ac9673665f996ef1371927f65f7..7b4bc494e7f123d867e26a5fda373c917c1d5737 100644 --- a/src/lib_scoru_wasm/wasm_vm.ml +++ b/src/lib_scoru_wasm/wasm_vm.ml @@ -26,6 +26,10 @@ module Wasm = Tezos_webassembly_interpreter open Wasm_pvm_state.Internal_state +let version_for_protocol : Pvm_input_kind.protocol -> Wasm_pvm_state.version = + function + | Proto_alpha -> V1 + let link_finished (ast : Wasm.Ast.module_) offset = offset >= Wasm.Ast.Vector.num_elements ast.it.imports @@ -616,8 +620,9 @@ let set_input_step input_info message pvm_state = let open Wasm_pvm_state in let {inbox_level; message_counter} = input_info in let raw_level = Bounded.Non_negative_int32.to_value inbox_level in + let return ?(durable = pvm_state.durable) x = Lwt.return (durable, x) in let return_stuck state_name = - Lwt.return + return (Stuck (Wasm_pvm_errors.invalid_state @@ Format.sprintf "No input required during %s" state_name)) @@ -625,19 +630,30 @@ let set_input_step input_info message pvm_state = let next_tick_state () = match pvm_state.tick_state with | Collect -> ( - let+ () = + let* () = Wasm.Input_buffer.( enqueue pvm_state.buffers.input {raw_level; message_counter; payload = String.to_bytes message}) in match Pvm_input_kind.from_raw_input message with - | Internal End_of_level -> Padding + | Internal End_of_level -> return Padding + | Internal (Protocol_migration proto) -> + let* durable = + Durable.set_value_exn + ~edit_readonly:true + pvm_state.durable + Constants.version_key + (Data_encoding.Binary.to_string_exn + Wasm_pvm_state.version_encoding + (version_for_protocol proto)) + in + return ~durable Collect | Internal Start_of_level -> update_output_buffer pvm_state raw_level ; - Collect - | _ -> Collect) - | Stuck _ -> Lwt.return pvm_state.tick_state + return Collect + | _ -> return Collect) + | Stuck _ -> return pvm_state.tick_state | Snapshot -> return_stuck "start" | Decode _ -> return_stuck "decoding" | Link _ -> return_stuck "link" @@ -645,10 +661,15 @@ let set_input_step input_info message pvm_state = | Eval _ -> return_stuck "evaluation" | Padding -> return_stuck "padding" in - let+ tick_state = Lwt.catch next_tick_state (exn_to_stuck pvm_state) in + let+ durable, tick_state = + Lwt.catch next_tick_state (fun exn -> + let+ tick_state = exn_to_stuck pvm_state exn in + (pvm_state.durable, tick_state)) + in (* Increase the current tick counter and update last input *) { pvm_state with + durable; tick_state; current_tick = Z.succ pvm_state.current_tick; last_input_info = Some input_info; @@ -677,4 +698,6 @@ module Internal_for_tests = struct let compute_step_many_with_hooks ?reveal_builtins ?write_debug ?after_fast_exec:_ = compute_step_many ?reveal_builtins ?write_debug + + let get_wasm_version = get_wasm_version end diff --git a/src/lib_scoru_wasm/wasm_vm_sig.ml b/src/lib_scoru_wasm/wasm_vm_sig.ml index 051b35512b3435730182912b9ed1212135f1d98d..faa756a6c22ece4aa1c63e4f9de2707ac7d9eac8 100644 --- a/src/lib_scoru_wasm/wasm_vm_sig.ml +++ b/src/lib_scoru_wasm/wasm_vm_sig.ml @@ -37,6 +37,8 @@ module type Internal_for_tests = sig max_steps:int64 -> state -> (state * int64) Lwt.t + + val get_wasm_version : state -> version Lwt.t end module type Generic = sig 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 115f6fc00cb7d6cada403e2520f190c658c25d44..7093c2786b2a2fc6afacc3eecbe80be7a220ba1d 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 @@ -305,6 +305,38 @@ let test_output () = fail_unless valid (Exn (Failure "An output proof is not valid.")) | Error _ -> failwith "Error during proof generation" +(* When snapshoting a new protocol, to fix this test, the following + action should be done. + + - In [src/lib_scoru_wasm/constants.ml], add a new variable before + [proto_alpha_name] using the name of the new protocol, and whose + value is [Raw_context.version_value]. + - Update [src/lib_scoru_wasm/pvm_input_kind.ml] to add a new case + to the type [protocol], and update the functions + [protocol_from_raw] and [Internal_for_tests.proto_to_binary] + accordingly (by copy/pasting the [Proto_alpha] case and doing + the necessary renaming. + - Update [src/lib_scoru_wasm/wasm_vm.ml], more precisely the + [version_for_protocol] function, to take into account the new + protocol. The expected result is the same as for + [Proto_alpha]. *) +let test_protocol_names () = + let open Alpha_context.Sc_rollup.Inbox_message in + let protocol_migration_message_str = + Data_encoding.Binary.to_string_exn + encoding + (Internal protocol_migration_internal_message) + in + let kind = + Tezos_scoru_wasm.Pvm_input_kind.from_raw_input + protocol_migration_message_str + in + assert (kind = Internal (Protocol_migration Proto_alpha)) ; + assert ( + protocol_migration_internal_message + = Protocol_migration Tezos_scoru_wasm.Constants.proto_alpha_name) ; + Lwt_result_syntax.return_unit + let tests = [ Tztest.tztest @@ -314,4 +346,5 @@ let tests = 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; + Tztest.tztest "test protocol names consistency" `Quick test_protocol_names; ]