diff --git a/src/lib_scoru_wasm/durable.ml b/src/lib_scoru_wasm/durable.ml new file mode 100644 index 0000000000000000000000000000000000000000..76833e6ddfc2026134dd802d49e0a9aa3db62620 --- /dev/null +++ b/src/lib_scoru_wasm/durable.ml @@ -0,0 +1,92 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 TriliTech *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +module T = Tree_encoding.Wrapped +module Runner = Tree_encoding.Runner.Make (Tree_encoding.Wrapped) +module E = Tree_encoding + +type t = T.tree + +exception Invalid_key of string + +exception Not_found + +let encoding = E.wrapped_tree + +let of_tree = T.select + +let to_tree = T.wrap + +type key = string list + +(* A key is bounded to 250 bytes, including the implicit '/durable' prefix. + Additionally, values are implicitly appended with '_'. **) +let max_prefix_key_length = 250 - String.length "/durable" - String.length "/_" + +let key_of_string_exn s = + if String.length s > max_prefix_key_length then raise (Invalid_key s) ; + let key = + match String.split '/' s with + | "" :: tl -> tl (* Must start with '/' *) + | _ -> raise (Invalid_key s) + in + let assert_valid_char = function + | '.' | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' -> () + | _ -> raise (Invalid_key s) + in + let all_steps_valid = + List.for_all (fun x -> + x <> "" + && + (String.iter assert_valid_char x ; + true)) + in + if all_steps_valid key then key else raise (Invalid_key s) + +(** We append all values with '_', which is an invalid key-character w.r.t. + external use. + + This ensures that an external user is prevented from accidentally writing a + value to a place which is part of another value (e.g. writing a + chunked_byte_vector to "/a/length", where "/a/length" previously existed as + part of another chunked_byte_vector encoding.) +*) +let to_value_key k = List.append k ["_"] + +let find_value tree key = + let open Lwt.Syntax in + let* opt = T.find_tree tree @@ to_value_key key in + match opt with + | None -> Lwt.return_none + | Some subtree -> + let+ value = Runner.decode E.chunked_byte_vector subtree in + Some value + +let find_value_exn tree key = + let open Lwt.Syntax in + let* opt = T.find_tree tree @@ to_value_key key in + match opt with + | None -> raise Not_found + | Some subtree -> Runner.decode E.chunked_byte_vector subtree diff --git a/src/lib_scoru_wasm/durable.mli b/src/lib_scoru_wasm/durable.mli new file mode 100644 index 0000000000000000000000000000000000000000..941d19a4d6337c36b47939b9a71d52a3d75645ea --- /dev/null +++ b/src/lib_scoru_wasm/durable.mli @@ -0,0 +1,58 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 TriliTech *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** [t] allows a [wrapped_tree] to be manipulated as a tree of + [chunked_byte_vector] *) +type t + +(** [key] was too long, or contained invalid steps. *) +exception Invalid_key of string + +(** A value was not found in the durable store. *) +exception Not_found + +(** [encoding] is a [Tree_encoding] for [t]. *) +val encoding : t Tree_encoding.t + +val of_tree : Lazy_containers.Lazy_map.tree -> t + +val to_tree : t -> Lazy_containers.Lazy_map.tree + +(** [key] is the type that indexes [t]. It enforces several constraints: + - a key's length is bounded. + - a key is a series of non-empty steps, where + - a step is preceded by '/' + - a step only contains alphanumeric ascii, or dots ('.') *) +type key + +(** raise @Invalid_key *) +val key_of_string_exn : string -> key + +(** [find_value durable key] optionally looks for the value encoded at [key] + in [durable]. *) +val find_value : t -> key -> Lazy_containers.Chunked_byte_vector.t option Lwt.t + +(** raise @Not_found *) +val find_value_exn : t -> key -> Lazy_containers.Chunked_byte_vector.t Lwt.t diff --git a/src/lib_scoru_wasm/gather_floppies.ml b/src/lib_scoru_wasm/gather_floppies.ml index 6a26b74ba7be4bedd70e6be690892ca6d128f470..56f94b729d7b612266ab4d29d41c108c6f2bc3c1 100644 --- a/src/lib_scoru_wasm/gather_floppies.ml +++ b/src/lib_scoru_wasm/gather_floppies.ml @@ -156,7 +156,7 @@ module Make (value ["gather-floppies"; "last-input-info"] @@ Data_encoding.option input_info_encoding) (value ["gather-floppies"; "internal-tick"] Data_encoding.n) - (scope ["durable"; "kernel"; "boot.wasm"] chunked_byte_vector) + (scope ["durable"; "kernel"; "boot.wasm"; "_"] chunked_byte_vector) (** [increment_ticks state] increments the number of ticks as stored in [state], or set it to [1] in case it has not been initialized diff --git a/src/lib_scoru_wasm/test/test_durable_storage.ml b/src/lib_scoru_wasm/test/test_durable_storage.ml new file mode 100644 index 0000000000000000000000000000000000000000..16eeeb8b9c6258abc01c93698daa70eb2981507a --- /dev/null +++ b/src/lib_scoru_wasm/test/test_durable_storage.ml @@ -0,0 +1,133 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 TriliTech *) +(* *) +(* 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 durable + Invocation: dune exec src/lib_scoru_wasm/test/test_scoru_wasm.exe \ + -- test "Durable storage" + Subject: Durable storage tests for the tezos-scoru-wasm library +*) + +open Tztest +open Lazy_containers +open Tezos_scoru_wasm +include Test_encodings_util +module Wasm = Wasm_pvm.Make (Tree) +module Wrapped_tree_runner = Tree_encoding.Runner.Make (Tree_encoding.Wrapped) + +let wrap_as_durable tree = + let open Lwt.Syntax in + let* tree = + Tree_encoding_runner.encode + (Tree_encoding.value ["durable"; "_keep_me"] Data_encoding.bool) + true + tree + in + let+ tree = + Tree_encoding_runner.decode + (Tree_encoding.scope ["durable"] Tree_encoding.wrapped_tree) + tree + in + Tree_encoding.Wrapped.wrap tree + +let assert_invalid_key run = + let open Lwt_syntax in + Lwt.catch + (fun () -> + let+ _ = run () in + assert false) + (fun caught_exn -> + match caught_exn with + | Durable.Invalid_key _ -> Lwt.return_ok () + | x -> raise x) + +(* Test that find_value/find_value_exn correctly decode the + chunked_byte_vector *) +let test_durable_find_value () = + let open Lwt_syntax in + let* tree = empty_tree () in + let value = Chunked_byte_vector.of_string "a very long value" in + let* tree = + Tree_encoding_runner.encode + (Tree_encoding.scope + ["durable"; "hello"; "value"; "_"] + Tree_encoding.chunked_byte_vector) + value + tree + in + let* tree = wrap_as_durable tree in + let durable = Durable.of_tree tree in + let* r = + Durable.find_value durable @@ Durable.key_of_string_exn "/hello/value" + in + assert (Option.is_some r) ; + let* x = + match r with + | Some y -> Chunked_byte_vector.to_string y + | None -> assert false + in + assert (x = "a very long value") ; + let* v = + Durable.find_value_exn durable @@ Durable.key_of_string_exn "/hello/value" + in + let* x = Chunked_byte_vector.to_string v in + assert (x = "a very long value") ; + let* r = + Durable.find_value durable @@ Durable.key_of_string_exn "/hello/other" + in + assert (Option.is_none r) ; + Lwt.return_ok () + +(* Test invalid key encodings are rejected. *) +let test_durable_invalid_keys () = + let open Lwt.Syntax in + let* _ = + assert_invalid_key (fun () -> + Lwt.return @@ Durable.key_of_string_exn "//hello") + in + let* _ = + assert_invalid_key (fun () -> + Lwt.return @@ Durable.key_of_string_exn "hello") + in + let* _ = + assert_invalid_key (fun () -> + Lwt.return @@ Durable.key_of_string_exn "/hello//world") + in + let* _ = + assert_invalid_key (fun () -> + Lwt.return @@ Durable.key_of_string_exn "/invalid_/key") + in + let* _ = + assert_invalid_key (fun () -> + Lwt.return @@ Durable.key_of_string_exn "/!\"?I") + in + Lwt.return_ok () + +let tests = + [ + tztest "Durable: find value" `Quick test_durable_find_value; + tztest "Durable: invalid keys" `Quick test_durable_invalid_keys; + ] diff --git a/src/lib_scoru_wasm/test/test_scoru_wasm.ml b/src/lib_scoru_wasm/test/test_scoru_wasm.ml index a3331bc02d6f505d058c169332d39ebfdb25f9b4..dde2c3f85b0e0f8d1f0874635a9f7f01de81b793 100644 --- a/src/lib_scoru_wasm/test/test_scoru_wasm.ml +++ b/src/lib_scoru_wasm/test/test_scoru_wasm.ml @@ -37,6 +37,7 @@ let () = ("Input", Test_input.tests); ("Output", Test_output.tests); ("Set/get", Test_get_set.tests); + ("Durable storage", Test_durable_storage.tests); ("AST Generators", Test_ast_generators.tests); ("WASM Encodings", Test_wasm_encoding.tests); ("WASM PVM Encodings", Test_wasm_pvm_encodings.tests); diff --git a/src/lib_scoru_wasm/wasm_pvm.ml b/src/lib_scoru_wasm/wasm_pvm.ml index c502f00016dfe8f375586f45c9f7f5f172b7fc1c..4dad589d008e2593020529ed11ea2945688216a6 100644 --- a/src/lib_scoru_wasm/wasm_pvm.ml +++ b/src/lib_scoru_wasm/wasm_pvm.ml @@ -47,7 +47,7 @@ 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. *) - kernel : Lazy_containers.Chunked_byte_vector.t; (** The loaded kernel. *) + durable : Durable.t; (** The durable storage of the PVM. *) module_reg : Wasm.Instance.module_reg; (** Module registry of the loaded kernel. *) tick_state : tick_state; (** The current tick state. *) @@ -128,14 +128,14 @@ struct conv (fun ( last_input_info, current_tick, - kernel, + durable, module_reg, tick_state, input_request ) -> { last_input_info; current_tick; - kernel; + durable; module_reg; tick_state; input_request; @@ -143,14 +143,14 @@ struct (fun { last_input_info; current_tick; - kernel; + durable; module_reg; tick_state; input_request; } -> ( last_input_info, current_tick, - kernel, + durable, module_reg, tick_state, input_request )) @@ -158,12 +158,14 @@ struct ~flatten:true (value_option ["wasm"; "input"] Wasm_pvm_sig.input_info_encoding) (value ~default:Z.zero ["wasm"; "current_tick"] Data_encoding.n) - (scope ["durable"; "kernel"; "boot.wasm"] chunked_byte_vector) + (scope ["durable"] Durable.encoding) (scope ["modules"] Wasm_encoding.module_instances_encoding) (scope ["wasm"] tick_state_encoding) (scope ["input"; "consuming"] input_request_encoding)) - let unsafe_next_tick_state {module_reg; kernel; tick_state; _} = + let kernel_key = Durable.key_of_string_exn "/kernel/boot.wasm" + + let unsafe_next_tick_state {module_reg; durable; tick_state; _} = let open Lwt_syntax in match tick_state with | Decode {module_kont = MKStop ast_module; _} -> @@ -172,6 +174,7 @@ struct module registry, why we can ignore the result here. *) Lwt.return (Init {self; ast_module; init_kont = IK_Start}) | Decode m -> + let* kernel = Durable.find_value_exn durable kernel_key in let+ m = Tezos_webassembly_interpreter.Decode.module_step kernel m in Decode m | Init {self; ast_module = _; init_kont = IK_Stop _module_inst} -> ( diff --git a/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml b/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml index f5250f88aa3b4f15a200fd3d70c63048b529b282..02f3faa28eb1d1ac51d3b153ed6799b46ec06596 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml @@ -178,7 +178,10 @@ let find_status tree = let get_chunks_count tree = let open Lwt.Syntax in let+ len = - find tree ["durable"; "kernel"; "boot.wasm"; "length"] Data_encoding.int64 + find + tree + ["durable"; "kernel"; "boot.wasm"; "_"; "length"] + Data_encoding.int64 in Option.fold ~none:0 ~some:Int64.to_int len