diff --git a/src/lib_scoru_wasm/durable.ml b/src/lib_scoru_wasm/durable.ml index 2ef564c47f551394f8de10288775611ea51a6c44..a1a85f264b846c17db75e279b0442adeb184ee2a 100644 --- a/src/lib_scoru_wasm/durable.ml +++ b/src/lib_scoru_wasm/durable.ml @@ -97,6 +97,17 @@ let find_value_exn tree key = | None -> raise Not_found | Some subtree -> Runner.decode E.chunked_byte_vector subtree +(** helper function used in the copy/move *) +let find_tree_exn tree key = + let open Lwt.Syntax in + let* opt = T.find_tree tree key in + match opt with None -> raise Not_found | Some subtree -> Lwt.return subtree + +let copy_tree_exn tree from_key to_key = + let open Lwt.Syntax in + let* move_tree = find_tree_exn tree from_key in + T.add_tree tree to_key move_tree + let count_subtrees tree key = let open Lwt.Syntax in let* opt = T.find_tree tree @@ to_value_key key in diff --git a/src/lib_scoru_wasm/durable.mli b/src/lib_scoru_wasm/durable.mli index fc1cff654e5832b96a8b8d16bc781f0b79899e8a..c746aac299480e44e934aa42ec49357277a6c418 100644 --- a/src/lib_scoru_wasm/durable.mli +++ b/src/lib_scoru_wasm/durable.mli @@ -67,6 +67,10 @@ 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 +(** [copy_tree_exn tree from_key to_key] produces a new tree in which a copy of + the entire subtree at from_key is copied at to_key.*) +val copy_tree_exn : t -> key -> key -> t Lwt.t + (** [count_subtrees durable key] returns the number of subtrees under [key]. *) val count_subtrees : t -> key -> int Lwt.t diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 06f50edc1de373c384fb92ce3afd46adb05c3778..a4bf310aed9283813faa01a477622c50f2baee1e 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -301,6 +301,53 @@ let store_list_size = (durable, [Values.(Num (I64 (I64.of_int_s num_subtrees)))]) | _ -> raise Bad_input) +let store_copy_name = "tezos_store_copy" + +let store_copy_type = + let input_types = + Types.[NumType I32Type; NumType I32Type; NumType I32Type; NumType I32Type] + |> Vector.of_list + in + let output_types = Vector.of_list [] in + Types.FuncType (input_types, output_types) + +let store_copy_aux durable memories from_key_offset from_key_length + to_key_offset to_key_length = + let open Lwt_syntax in + let from_key_length = Int32.to_int from_key_length in + let to_key_length = Int32.to_int to_key_length in + if from_key_length > Durable.max_key_length then + raise (Key_too_large from_key_length) ; + if to_key_length > Durable.max_key_length then + raise (Key_too_large to_key_length) ; + let* memory = retrieve_memory memories in + let* from_key = Memory.load_bytes memory from_key_offset from_key_length in + let* to_key = Memory.load_bytes memory to_key_offset to_key_length in + let tree = Durable.of_storage_exn durable in + let from_key = Durable.key_of_string_exn from_key in + let to_key = Durable.key_of_string_exn to_key in + let+ tree = Durable.copy_tree_exn tree from_key to_key in + (Durable.to_storage tree, []) + +let store_copy = + Host_funcs.Host_func + (fun _input_buffer _output_buffer durable memories inputs -> + match inputs with + | [ + Values.(Num (I32 from_key_offset)); + Values.(Num (I32 from_key_length)); + Values.(Num (I32 to_key_offset)); + Values.(Num (I32 to_key_length)); + ] -> + store_copy_aux + durable + memories + from_key_offset + from_key_length + to_key_offset + to_key_length + | _ -> raise Bad_input) + let lookup_opt name = match name with | "read_input" -> @@ -314,6 +361,8 @@ let lookup_opt name = Some (ExternFunc (HostFunc (store_list_size_type, store_list_size_name))) | "store_delete" -> Some (ExternFunc (HostFunc (store_delete_type, store_delete_name))) + | "store_copy" -> + Some (ExternFunc (HostFunc (store_copy_type, store_copy_name))) | _ -> None let lookup name = @@ -331,6 +380,7 @@ let register_host_funcs registry = (store_has_name, store_has); (store_list_size_name, store_list_size); (store_delete_name, store_delete); + (store_copy_name, store_copy); ] module Internal_for_tests = struct @@ -346,6 +396,8 @@ module Internal_for_tests = struct let store_delete = Func.HostFunc (store_delete_type, store_delete_name) + let store_copy = Func.HostFunc (store_copy_type, store_copy_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 7e3b96fa965018986d10345aef3f286d82f43e57..b5eec35e967924801c0713720ac3e8cfecc2ea00 100644 --- a/src/lib_scoru_wasm/host_funcs.mli +++ b/src/lib_scoru_wasm/host_funcs.mli @@ -107,5 +107,7 @@ module Internal_for_tests : sig val store_delete : Tezos_webassembly_interpreter.Instance.func_inst + val store_copy : 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 0d4883a85cbdb061b516714f3726f711aca011de..038ac15a81a4cd0ee91f49c0f7d67b5b92cc34d8 100644 --- a/src/lib_scoru_wasm/test/test_durable_storage.ml +++ b/src/lib_scoru_wasm/test/test_durable_storage.ml @@ -39,6 +39,12 @@ include Test_encodings_util module Wasm = Wasm_pvm.Make (Tree) module Wrapped_tree_runner = Tree_encoding.Runner.Make (Tree_encoding.Wrapped) +let equal_chunks c1 c2 = + let open Lwt.Syntax in + let* c1 = Chunked_byte_vector.to_string c1 in + let* c2 = Chunked_byte_vector.to_string c2 in + Lwt.return @@ assert (String.equal c1 c2) + let wrap_as_durable_storage tree = let open Lwt.Syntax in let* tree = @@ -72,7 +78,6 @@ let test_store_has_missing_key () = 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 ; @@ -449,6 +454,105 @@ let test_durable_count_subtrees () = let* () = assert_subtree_count tree 0 "/bye" in Lwt.return_ok () +(* Test checking that [store_copy from_key to_key] copies the subtree at + [from_key] to [to_key] in the durable storage. This should overwrite + the tree that existed previously at [to_key] *) +let test_store_copy () = + 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/_ = "a very long value" + /durable/a/short/path/one/_ = "a very long value" + /durable/a/long/path/two/_ = "a very long value" + *) + 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"; "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 from_key = "/a/short/path/one" in + let to_key = "/a/long/path" in + let wrong_key = "/a/long/path/two" in + let durable_st = Durable.of_storage_exn durable in + let* old_value_from_key = + Durable.find_value_exn durable_st @@ Durable.key_of_string_exn from_key + in + let* old_value_at_two = + Durable.find_value_exn durable_st @@ Durable.key_of_string_exn wrong_key + in + let* () = equal_chunks old_value_at_two (value ()) in + let from_offset = 20l in + let to_offset = 40l in + let _ = Memory.store_bytes memory from_offset from_key in + let _ = Memory.store_bytes memory to_offset to_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 from_offset); + Num (I32 (Int32.of_int @@ String.length from_key)); + Num (I32 to_offset); + Num (I32 (Int32.of_int @@ String.length to_key)); + ] + in + let* durable, result = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable + host_funcs_registry + Host_funcs.Internal_for_tests.store_copy + values + in + assert (result = []) ; + let durable = Durable.of_storage_exn durable in + + let* new_value_at_two = + Durable.find_value durable @@ Durable.key_of_string_exn wrong_key + in + let* new_value_from_key = + Durable.find_value_exn durable @@ Durable.key_of_string_exn from_key + in + let* new_value_to_key = + Durable.find_value_exn durable @@ Durable.key_of_string_exn to_key + in + assert (new_value_at_two = None) ; + let* () = equal_chunks new_value_from_key new_value_to_key in + let* () = equal_chunks old_value_from_key new_value_from_key in + Lwt.return_ok () + (* Test invalid key encodings are rejected. *) let test_durable_invalid_keys () = let open Lwt.Syntax in @@ -481,6 +585,7 @@ let tests = 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 "store_copy" `Quick test_store_copy; 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;