diff --git a/src/lib_scoru_wasm/durable.ml b/src/lib_scoru_wasm/durable.ml index 16cf02a5e51177ae0b438f69e7ce528303ce9385..ac71f903b94c9fb880b11c1d1dae565498f8c2d9 100644 --- a/src/lib_scoru_wasm/durable.ml +++ b/src/lib_scoru_wasm/durable.ml @@ -102,3 +102,5 @@ let count_subtrees tree key = let* opt = T.find_tree tree @@ to_value_key key in let+ len = T.length tree key in if Option.is_none opt then len else len - 1 + +let delete tree key = T.remove tree key diff --git a/src/lib_scoru_wasm/durable.mli b/src/lib_scoru_wasm/durable.mli index 4aad6ca565d6012dd8aa59e050d40b30f53c93fb..2906784ab73afba175653281c4a845a7c2eaca22 100644 --- a/src/lib_scoru_wasm/durable.mli +++ b/src/lib_scoru_wasm/durable.mli @@ -69,3 +69,6 @@ val find_value_exn : t -> key -> Lazy_containers.Chunked_byte_vector.t Lwt.t (** [count_subtrees durable key] returns the number of subtrees under [key]. *) val count_subtrees : t -> key -> int Lwt.t + +(** [delete durable key] deletes the value at and/or subtrees of [key]. *) +val delete : t -> key -> t Lwt.t diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 7a6555f519ed056b37e51dc6b09db5d79b4bb078..758a69dc7b7ef0f85ff27abfe4db2e915c59f1eb 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_delete_name = "tezos_store_delete" + +let store_delete_type = + let input_types = + Types.[NumType I32Type; NumType I32Type] |> Vector.of_list + in + let output_types = Vector.of_list [] in + Types.FuncType (input_types, output_types) + +let store_delete = + 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+ tree = Durable.delete tree key in + (Durable.to_storage tree, []) + | _ -> raise Bad_input) + let store_list_size_name = "tezos_store_list_size" let store_list_size_type = @@ -284,6 +310,8 @@ let lookup_opt 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))) + | "store_delete" -> + Some (ExternFunc (HostFunc (store_delete_type, store_delete_name))) | _ -> None let lookup name = @@ -300,6 +328,7 @@ let register_host_funcs registry = (write_debug_name, write_debug); (store_has_name, store_has); (store_list_size_name, store_list_size); + (store_delete_name, store_delete); ] module Internal_for_tests = struct @@ -313,6 +342,8 @@ module Internal_for_tests = struct let store_has = Func.HostFunc (store_has_type, store_has_name) + let store_delete = Func.HostFunc (store_delete_type, store_delete_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 57e5d0251766462eb339c6006162d624ca911f43..7e3b96fa965018986d10345aef3f286d82f43e57 100644 --- a/src/lib_scoru_wasm/host_funcs.mli +++ b/src/lib_scoru_wasm/host_funcs.mli @@ -105,5 +105,7 @@ module Internal_for_tests : sig *) val store_has : Tezos_webassembly_interpreter.Instance.func_inst + val store_delete : 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 2a538a1e409e2d2f12e31fa535e8239dc555b9d2..0d4883a85cbdb061b516714f3726f711aca011de 100644 --- a/src/lib_scoru_wasm/test/test_durable_storage.ml +++ b/src/lib_scoru_wasm/test/test_durable_storage.ml @@ -221,6 +221,87 @@ let test_store_list_size () = assert (result = Values.[Num (I64 (I64.of_int_s 2))]) ; Lwt.return_ok () +(* Test checking that [store_delete key] deletes the subtree at [key] from the + durable storage. *) +let test_store_delete () = + 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/long/path/_ = "..." + + We expect that deleting "/a/short/path" is leaves only "/durable/a/long/path". + *) + 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"; "a"; "long"; "path"; "_"] + 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* durable, result = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable + host_funcs_registry + Host_funcs.Internal_for_tests.store_delete + values + in + assert (result = []) ; + let durable = Durable.of_storage_exn durable in + let* value_opt = + Durable.find_value durable @@ Durable.key_of_string_exn key + in + assert (Option.is_none value_opt) ; + let* count = + Durable.count_subtrees durable @@ Durable.key_of_string_exn key + in + assert (count = 0) ; + let* value_opt = + Durable.find_value durable @@ Durable.key_of_string_exn "/a/long/path" + in + assert (Option.is_some value_opt) ; + 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 () = @@ -399,6 +480,7 @@ let tests = 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 "store_delete removes subtree" `Quick test_store_delete; 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 7adb088638cf2d7a7af21994f245a28ee412f5f3..de55959551517deda26e3cc831605260818a8f02 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -62,6 +62,14 @@ let test_store_has_kernel = "test-store-has" *) let test_store_list_size_kernel = "test-store-list-size" +(* Kernel checking the behaviour value of store_delete host func. + + This kernel deletes the following paths: + - `/durable/one` + - `/durable/three/four` +*) +let test_store_delete_kernel = "test-store-delete" + (** [check_error kind reason error] checks a Wasm PVM error [error] is of a given [kind] with a possible [reason]. @@ -242,6 +250,37 @@ let should_run_store_list_size_kernel kernel = (* The kernel is now expected to fail, the PVM should be in stuck state. *) assert (is_stuck state_after_second_message) +let should_run_store_delete_kernel kernel = + let open Lwt_syntax in + let open Test_encodings_util in + let* tree = initial_tree ~from_binary:true kernel in + let* tree = add_value tree ["one"] in + let* tree = add_value tree ["one"; "two"] in + let* tree = add_value tree ["three"] in + let* tree = add_value tree ["three"; "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. *) + (* Check that we have all the paths in place. *) + let* result = Tree.find_tree tree ["durable"; "one"] in + assert (Option.is_some result) ; + let* result = Tree.find_tree tree ["durable"; "three"; "four"] in + assert (Option.is_some result) ; + let* result = Tree.find_tree tree ["durable"; "three"] in + assert (Option.is_some result) ; + (* Eval until input requested *) + let* tree = eval_until_input_requested tree in + let* state = 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) ; + (* The paths /one & /three/four will have been deleted. *) + let* result = Tree.find_tree tree ["durable"; "one"] in + assert (Option.is_none result) ; + let* result = Tree.find_tree tree ["durable"; "three"; "four"] in + assert (Option.is_none result) ; + let+ result = Tree.find_tree tree ["durable"; "three"] in + assert (Option.is_some result) + let test_with_kernel kernel test () = let open Lwt_result_syntax in let open Tezt.Base in @@ -292,4 +331,8 @@ let tests = (test_with_kernel test_store_list_size_kernel should_run_store_list_size_kernel); + tztest + "Test store-delete kernel" + `Quick + (test_with_kernel test_store_delete_kernel should_run_store_delete_kernel); ] diff --git a/src/lib_scoru_wasm/test/wasm_kernels/README.md b/src/lib_scoru_wasm/test/wasm_kernels/README.md index 1a964275424ef6899fb5471bcdd0e617a970734b..04c0f9c8a966767b7505b0b617fc755cacce1f11 100644 --- a/src/lib_scoru_wasm/test/wasm_kernels/README.md +++ b/src/lib_scoru_wasm/test/wasm_kernels/README.md @@ -75,3 +75,15 @@ git checkout 0c98b17c4599d6f656312b16f17798406d491d77 ./scripts/build-unit-kernel.sh "test-store-list-size" ``` + +## [test-store-delete.wasm](./test-store-delete.wasm) +This kernel is designed to test the `store_delete` host function behaviour, on different keys in *durable storage*. + +It may be originated directly within a boot sector. + +To build the `test-store-delete.wasm` kernel, run the following from the checked-out `trili/kernel` repo: +```shell +git checkout 0c98b17c4599d6f656312b16f17798406d491d77 + +./scripts/build-unit-kernel.sh "test-store-delete" +``` diff --git a/src/lib_scoru_wasm/test/wasm_kernels/test-store-delete.wasm b/src/lib_scoru_wasm/test/wasm_kernels/test-store-delete.wasm new file mode 100755 index 0000000000000000000000000000000000000000..60ca9b71eb759fd0258472a070cb50db2379f719 Binary files /dev/null and b/src/lib_scoru_wasm/test/wasm_kernels/test-store-delete.wasm differ