diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 375da506b8747d3b4b5ca615a024539d9ed0296d..7a6555f519ed056b37e51dc6b09db5d79b4bb078 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -247,6 +247,32 @@ let store_has = (durable, [Values.(Num (I32 (I32.of_int_s r)))]) | _ -> raise Bad_input) +let store_list_size_name = "tezos_store_list_size" + +let store_list_size_type = + let input_types = + Types.[NumType I32Type; NumType I32Type] |> Vector.of_list + in + let output_types = Types.[NumType I64Type] |> Vector.of_list in + Types.FuncType (input_types, output_types) + +let store_list_size = + Host_funcs.Host_func + (fun _input_buffer _output_buffer durable memories inputs -> + let open Lwt.Syntax in + match inputs with + | [Values.(Num (I32 key_offset)); Values.(Num (I32 key_length))] -> + let key_length = Int32.to_int key_length in + if key_length > Durable.max_key_length then + raise (Key_too_large key_length) ; + let* memory = retrieve_memory memories in + let* key = Memory.load_bytes memory key_offset key_length in + let tree = Durable.of_storage_exn durable in + let key = Durable.key_of_string_exn key in + let+ num_subtrees = Durable.count_subtrees tree key in + (durable, [Values.(Num (I64 (I64.of_int_s num_subtrees)))]) + | _ -> raise Bad_input) + let lookup_opt name = match name with | "read_input" -> @@ -256,6 +282,8 @@ let lookup_opt name = | "write_debug" -> Some (ExternFunc (HostFunc (write_debug_type, write_debug_name))) | "store_has" -> Some (ExternFunc (HostFunc (store_has_type, store_has_name))) + | "store_list_size" -> + Some (ExternFunc (HostFunc (store_list_size_type, store_list_size_name))) | _ -> None let lookup name = @@ -271,6 +299,7 @@ let register_host_funcs registry = (write_output_name, write_output); (write_debug_name, write_debug); (store_has_name, store_has); + (store_list_size_name, store_list_size); ] module Internal_for_tests = struct @@ -283,4 +312,7 @@ module Internal_for_tests = struct let read_input = Func.HostFunc (read_input_type, read_input_name) let store_has = Func.HostFunc (store_has_type, store_has_name) + + let store_list_size = + Func.HostFunc (store_list_size_type, store_list_size_name) end diff --git a/src/lib_scoru_wasm/host_funcs.mli b/src/lib_scoru_wasm/host_funcs.mli index 1b6fdee5f133169b6e05191b7b92d8678d0a4640..57e5d0251766462eb339c6006162d624ca911f43 100644 --- a/src/lib_scoru_wasm/host_funcs.mli +++ b/src/lib_scoru_wasm/host_funcs.mli @@ -104,4 +104,6 @@ module Internal_for_tests : sig - [3]: There is a value at [key], and subtrees under [key]. *) val store_has : Tezos_webassembly_interpreter.Instance.func_inst + + val store_list_size : Tezos_webassembly_interpreter.Instance.func_inst end diff --git a/src/lib_scoru_wasm/test/test_durable_storage.ml b/src/lib_scoru_wasm/test/test_durable_storage.ml index f9f898802ef5477cf1e0af09b454980ae341534b..2a538a1e409e2d2f12e31fa535e8239dc555b9d2 100644 --- a/src/lib_scoru_wasm/test/test_durable_storage.ml +++ b/src/lib_scoru_wasm/test/test_durable_storage.ml @@ -150,6 +150,77 @@ let test_store_has_key_too_long () = assert (result = Values.[Num (I32 (I32.of_int_s 0))]) ; Lwt.return_ok () +(* Test checking that [store_list_size key] returns the number of immediate + subtrees. *) +let test_store_list_size () = + let open Lwt_syntax in + let* tree = empty_tree () in + let value () = Chunked_byte_vector.of_string "a very long value" in + (* + Store the following tree: + + /durable/a/short/path/_ = "..." + /durable/a/short/path/one/_ = "..." + /durable/a/short/path/two/_ = "..." + + We expect that the list size of "/a/short/path" is 2. + + Note that the value of "/durable/a/short/path/_" is not included in the listing. + *) + let key = "/a/short/path" in + let key_steps = ["a"; "short"; "path"] in + let* tree = + Tree_encoding_runner.encode + (Tree_encoding.scope + ("durable" :: List.append key_steps ["_"]) + Tree_encoding.chunked_byte_vector) + (value ()) + tree + in + let* tree = + Tree_encoding_runner.encode + (Tree_encoding.scope + ("durable" :: List.append key_steps ["one"; "_"]) + Tree_encoding.chunked_byte_vector) + (value ()) + tree + in + let* tree = + Tree_encoding_runner.encode + (Tree_encoding.scope + ("durable" :: List.append key_steps ["two"; "_"]) + Tree_encoding.chunked_byte_vector) + (value ()) + tree + in + let* durable = wrap_as_durable_storage tree in + let module_inst = Tezos_webassembly_interpreter.Instance.empty_module_inst in + let memory = Memory.alloc (MemoryType Types.{min = 20l; max = Some 3600l}) in + let src = 20l in + let _ = Memory.store_bytes memory src key in + let memories = Lazy_vector.Int32Vector.cons memory module_inst.memories in + let module_inst = {module_inst with memories} in + let host_funcs_registry = Tezos_webassembly_interpreter.Host_funcs.empty () in + Host_funcs.register_host_funcs host_funcs_registry ; + + let module_reg = Instance.ModuleMap.create () in + let module_key = Instance.Module_key "test" in + Instance.update_module_ref module_reg module_key module_inst ; + let values = + Values.[Num (I32 src); Num (I32 (Int32.of_int @@ String.length key))] + in + let* _, result = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable + host_funcs_registry + Host_funcs.Internal_for_tests.store_list_size + values + in + assert (result = Values.[Num (I64 (I64.of_int_s 2))]) ; + Lwt.return_ok () + (* Test checking that if [key] has value/subtree, [store_has key] returns the correct enum value. *) let test_store_has_existing_key () = @@ -327,6 +398,7 @@ let tests = tztest "store_has missing key" `Quick test_store_has_missing_key; tztest "store_has existing key" `Quick test_store_has_existing_key; tztest "store_has key too long key" `Quick test_store_has_key_too_long; + tztest "store_list_size counts subtrees" `Quick test_store_list_size; tztest "Durable: find value" `Quick test_durable_find_value; tztest "Durable: count subtrees" `Quick test_durable_count_subtrees; tztest "Durable: invalid keys" `Quick test_durable_invalid_keys; diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index 90b87dc85792e2d4028dbcf0d6201d4bc3263ea0..55b39ce6dfa8386728023420dbe346563caf37c1 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -52,6 +52,16 @@ let test_write_debug_kernel = "test-write-debug" *) let test_store_has_kernel = "test-store-has" +(* Kernel checking the return value of store_list_size host func. + + This kernel expects a collection of values to exist: + - `/durable/one/two` + - `/durable/one/three` + - `/durable/one/four` + and asserts that `store_list_size(/one) = 3`. +*) +let test_store_list_size_kernel = "test-store-list-size" + (** [check_error kind reason error] checks a Wasm PVM error [error] is of a given [kind] with a possible [reason]. @@ -181,20 +191,20 @@ let should_run_debug_kernel kernel = (* The kernel should not fail. *) assert (not @@ is_stuck state_after_first_message) +let add_value tree key_steps = + let open Lazy_containers in + let open Test_encodings_util in + let value = Chunked_byte_vector.of_string "a very long value" in + Tree_encoding_runner.encode + (Tree_encoding.scope + ("durable" :: List.append key_steps ["_"]) + Tree_encoding.chunked_byte_vector) + value + tree + let should_run_store_has_kernel kernel = let open Lwt_syntax in let* tree = initial_boot_sector_from_kernel kernel in - let add_value tree key_steps = - let open Lazy_containers in - let open Test_encodings_util in - let value = Chunked_byte_vector.of_string "a very long value" in - Tree_encoding_runner.encode - (Tree_encoding.scope - ("durable" :: List.append key_steps ["_"]) - Tree_encoding.chunked_byte_vector) - value - tree - in let* tree = add_value tree ["hi"; "bye"] in let* tree = add_value tree ["hello"] in let* tree = add_value tree ["hello"; "universe"] in @@ -220,6 +230,38 @@ let should_run_store_has_kernel kernel = (* The kernel is now expected to fail, the PVM should be in stuck state. *) assert (is_stuck state_after_first_message) +let should_run_store_list_size_kernel kernel = + let open Lwt_syntax in + let* tree = initial_boot_sector_from_kernel kernel in + let* tree = add_value tree ["one"; "two"] in + let* tree = add_value tree ["one"; "three"] in + let* tree = add_value tree ["one"; "four"] in + (* Make the first ticks of the WASM PVM (parsing of origination + message, parsing and init of the kernel), to switch it to + “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 + (* Adding a value at ["one"] should not affect the count. *) + let* tree = add_value tree ["one"] in + (* running until waiting for input *) + let* tree = eval_until_input_requested tree in + let* state_after_first_message = + Wasm.Internal_for_tests.get_tick_state tree + in + (* The kernel is not expected to fail, the PVM should not be in stuck state. *) + 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 = add_value tree ["one"; "five"] in + let* tree = eval_until_input_requested tree in + let+ state_after_second_message = + Wasm.Internal_for_tests.get_tick_state tree + in + (* The kernel is now expected to fail, the PVM should be in stuck state. *) + assert (is_stuck state_after_second_message) + let test_with_kernel kernel test () = let open Lwt_result_syntax in let open Tezt.Base in @@ -264,4 +306,10 @@ let tests = "Test store-has kernel" `Quick (test_with_kernel test_store_has_kernel should_run_store_has_kernel); + tztest + "Test store-list-size kernel" + `Quick + (test_with_kernel + test_store_list_size_kernel + should_run_store_list_size_kernel); ] diff --git a/src/lib_scoru_wasm/test/wasm_kernels/README.md b/src/lib_scoru_wasm/test/wasm_kernels/README.md index 403e7288af9fe8ecd7e9d742b3e3d4c15fea0a26..1a964275424ef6899fb5471bcdd0e617a970734b 100644 --- a/src/lib_scoru_wasm/test/wasm_kernels/README.md +++ b/src/lib_scoru_wasm/test/wasm_kernels/README.md @@ -63,3 +63,15 @@ git checkout 4788b8a882efbc9c19621ab43d617b2bdd5b1baf ./scripts/build-unit-kernel.sh "test-store-has" ``` + +## [test-store-list-size.wasm](./test-store-list-size.wasm) +This kernel is designed to test the `store_list_size` host function behaviour, on different keys in *durable storage*. + +It may be originated directly within a boot sector. + +To build the `test-store-list-size.wasm` kernel, run the following from the checked-out `trili/kernel` repo: +```shell +git checkout 0c98b17c4599d6f656312b16f17798406d491d77 + +./scripts/build-unit-kernel.sh "test-store-list-size" +``` diff --git a/src/lib_scoru_wasm/test/wasm_kernels/test-store-list-size.wasm b/src/lib_scoru_wasm/test/wasm_kernels/test-store-list-size.wasm new file mode 100755 index 0000000000000000000000000000000000000000..d3ee4c732230bcaa64b552f1030baa34cb624400 Binary files /dev/null and b/src/lib_scoru_wasm/test/wasm_kernels/test-store-list-size.wasm differ