diff --git a/src/lib_scoru_wasm/durable.ml b/src/lib_scoru_wasm/durable.ml index c202e7888817c9a7bb82aefb17834445e6415f65..58f16fed70d1506a20a725dbed25d3629b553eca 100644 --- a/src/lib_scoru_wasm/durable.ml +++ b/src/lib_scoru_wasm/durable.ml @@ -32,6 +32,8 @@ type t = T.tree exception Invalid_key of string +exception Index_too_large of int + exception Not_found exception Durable_empty = Storage.Durable_empty @@ -114,6 +116,16 @@ let count_subtrees tree key = T.length tree key let delete tree key = T.remove tree key +let subtree_name_at tree key index = + let open Lwt.Syntax in + let* subtree = find_tree_exn tree key in + let* list = T.list ~offset:index ~length:1 subtree [] in + let nth = List.nth list 0 in + match nth with + | Some ("_", _) -> Lwt.return "" + | Some (step, _) -> Lwt.return step + | None -> raise (Index_too_large index) + let move_tree_exn tree from_key to_key = let open Lwt.Syntax in let* tree = copy_tree_exn tree from_key to_key in diff --git a/src/lib_scoru_wasm/durable.mli b/src/lib_scoru_wasm/durable.mli index bad989aedb1afe7f75fa20676fb3b809af724156..1ea14b12b6aa07b4cfe415d856065140907130c7 100644 --- a/src/lib_scoru_wasm/durable.mli +++ b/src/lib_scoru_wasm/durable.mli @@ -84,6 +84,10 @@ val move_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 +(** [subtree_name_at durable key n] returns the name of the n_th subtree + under [key]. *) +val subtree_name_at : t -> key -> int -> string 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 669b0dbd095e74e08415f5126edc47d7fb01ccf6..33199e5448ae50452f7041919c4cfcfc74a3e58e 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -303,6 +303,72 @@ let store_list_size = (durable, [Values.(Num (I64 (I64.of_int_s num_subtrees)))]) | _ -> raise Bad_input) +let store_get_nth_key_name = "tezos_store_get_nth_key_list" + +let store_get_nth_key_type = + let input_types = + Types. + [ + NumType I32Type; + NumType I32Type; + NumType I64Type; + NumType I32Type; + NumType I32Type; + ] + |> Vector.of_list + in + let output_types = Types.[NumType I32Type] |> Vector.of_list in + Types.FuncType (input_types, output_types) + +let store_get_nth_key_aux durable memories key_offset key_length index dst + max_size = + let open Lwt.Syntax in + let index = Int64.to_int index in + 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* result = Durable.subtree_name_at tree key index in + + let result_size = String.length result in + let max_size = Int32.to_int max_size in + let result = + if max_size < result_size then String.sub result 0 max_size else result + in + let* _ = + if result <> "" then Memory.store_bytes memory dst result + else Lwt.return_unit + in + Lwt.return (Int32.of_int @@ String.length result) + +let store_get_nth_key = + 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); + Num (I32 key_length); + Num (I64 index); + Num (I32 dst); + Num (I32 max_size); + ] -> + let+ result = + store_get_nth_key_aux + durable + memories + key_offset + key_length + index + dst + max_size + in + (durable, [Values.(Num (I32 result))]) + | _ -> raise Bad_input) + let store_copy_name = "tezos_store_copy" let store_copy_type = @@ -540,6 +606,9 @@ 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_get_nth_key" -> + Some + (ExternFunc (HostFunc (store_get_nth_key_type, store_get_nth_key_name))) | "store_delete" -> Some (ExternFunc (HostFunc (store_delete_type, store_delete_name))) | "store_copy" -> @@ -568,6 +637,7 @@ let register_host_funcs registry = (write_debug_name, write_debug); (store_has_name, store_has); (store_list_size_name, store_list_size); + (store_get_nth_key_name, store_get_nth_key); (store_delete_name, store_delete); (store_copy_name, store_copy); (store_move_name, store_move); @@ -599,4 +669,7 @@ module Internal_for_tests = struct let store_list_size = Func.HostFunc (store_list_size_type, store_list_size_name) + + let store_get_nth_key = + Func.HostFunc (store_get_nth_key_type, store_get_nth_key_name) end diff --git a/src/lib_scoru_wasm/host_funcs.mli b/src/lib_scoru_wasm/host_funcs.mli index 4d522fb3afa28b105fd6a9c90194e9a5dc728a8f..b5b70a0a2d61f2f076da6ac133a186fbf84ec69a 100644 --- a/src/lib_scoru_wasm/host_funcs.mli +++ b/src/lib_scoru_wasm/host_funcs.mli @@ -116,4 +116,6 @@ module Internal_for_tests : sig val store_write : Tezos_webassembly_interpreter.Instance.func_inst val store_list_size : Tezos_webassembly_interpreter.Instance.func_inst + + val store_get_nth_key : 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 36bdc2cfbc281cf23b2ed69dfe2d3fc6add9db55..bc76db05c7d70225c7fe7fd9bf8b244c3ffd5c62 100644 --- a/src/lib_scoru_wasm/test/test_durable_storage.ml +++ b/src/lib_scoru_wasm/test/test_durable_storage.ml @@ -165,6 +165,159 @@ let test_store_list_size () = assert (result = Values.[Num (I64 (I64.of_int_s 3))]) ; Lwt.return_ok () +(* Test checking that [store_get_nth_key key index dst max_size] returns the size + of the name of the immediate subtree at [index]. *) +let test_store_get_nth_key () = + let open Lwt_syntax in + (* + Store the following tree: + + /durable/a/short/path/_ = "..." + /durable/a/short/path/one/_ = "..." + /durable/a/short/path/three/_ = "..." + + We expect that the result at "/a/short/path/one" is 3 and at + /durable/a/short/path/three 5. We also expect the truncated at 3 + result at /durable/a/short/path/three to be 3 + *) + let* durable = + make_durable + [ + ("/a/short/path", "true"); + ("/a/short/path/one", "true"); + ("/a/short/path/three", "true"); + ] + in + let key = "/a/short/path" in + let src = 20l in + let module_reg, module_key, host_funcs_registry = + make_module_inst [key] src + in + let key_length = Int32.of_int @@ String.length key in + let dst_zero = 20l in + let expected_string_at_zero = "" in + let dst_one = 70l in + let expected_string_at_one = "one" in + let dst_two = 80l in + let expected_string_at_two = "three" in + let truncated_dst_two = 100l in + let expected_truncated_string_at_two = "thr" in + + let wrong_value = + Values. + [ + Num (I32 0l); + Num (I32 2l); + Num (I64 0L); + Num (I32 dst_zero); + Num (I32 3600l); + ] + in + let _ = + try + let _ = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable + host_funcs_registry + Host_funcs.Internal_for_tests.store_get_nth_key + wrong_value + in + () + with e -> ( match e with Not_found -> () | _ -> assert false) + in + let value_at_zero = + Values. + [ + Num (I32 src); + Num (I32 key_length); + Num (I64 0L); + Num (I32 dst_zero); + Num (I32 3600l); + ] + in + let* _, result_at_zero = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable + host_funcs_registry + Host_funcs.Internal_for_tests.store_get_nth_key + value_at_zero + in + let value_at_one = + Values. + [ + Num (I32 src); + Num (I32 key_length); + Num (I64 1L); + Num (I32 dst_one); + Num (I32 3600l); + ] + in + let* _, result_at_one = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable + host_funcs_registry + Host_funcs.Internal_for_tests.store_get_nth_key + value_at_one + in + let value_at_two = + Values. + [ + Num (I32 src); + Num (I32 key_length); + Num (I64 2L); + Num (I32 dst_two); + Num (I32 3600l); + ] + in + let* _, result_at_two = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable + host_funcs_registry + Host_funcs.Internal_for_tests.store_get_nth_key + value_at_two + in + let truncated_value_at_two = + Values. + [ + Num (I32 src); + Num (I32 key_length); + Num (I64 2L); + Num (I32 truncated_dst_two); + Num (I32 3l); + ] + in + let* _, truncated_result_at_two = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable + host_funcs_registry + Host_funcs.Internal_for_tests.store_get_nth_key + truncated_value_at_two + in + let* memory = retrieve_memory module_reg in + let* string_at_zero = Memory.load_bytes memory dst_zero 0 in + let* string_at_one = Memory.load_bytes memory dst_one 3 in + let* string_at_two = Memory.load_bytes memory dst_two 5 in + let* truncated_string_at_two = Memory.load_bytes memory truncated_dst_two 3 in + assert (result_at_zero = Values.[Num (I32 (I32.of_int_s 0))]) ; + assert (string_at_zero = expected_string_at_zero) ; + assert (result_at_one = Values.[Num (I32 (I32.of_int_s 3))]) ; + assert (string_at_one = expected_string_at_one) ; + assert (result_at_two = Values.[Num (I32 (I32.of_int_s 5))]) ; + assert (string_at_two = expected_string_at_two) ; + assert (truncated_result_at_two = Values.[Num (I32 (I32.of_int_s 3))]) ; + assert (truncated_string_at_two = expected_truncated_string_at_two) ; + Lwt.return_ok () + (* Test checking that [store_delete key] deletes the subtree at [key] from the durable storage. *) let test_store_delete () = @@ -342,7 +495,6 @@ let test_store_copy () = in let from_offset = src in let from_length = Int32.of_int @@ String.length from_key in - Printf.printf "fl= %li" from_length ; let to_offset = Int32.(add from_offset from_length) in let to_length = Int32.of_int @@ String.length to_key in let durable_st = Durable.of_storage_exn durable in @@ -623,6 +775,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_get_nth_key produces subtrees" `Quick test_store_get_nth_key; tztest "store_delete removes subtree" `Quick test_store_delete; tztest "store_copy" `Quick test_store_copy; tztest "store_move" `Quick test_store_move; diff --git a/src/lib_scoru_wasm/test/wasm_utils.ml b/src/lib_scoru_wasm/test/wasm_utils.ml index c98e3337fec3cff9bdce013d864d8ef56964c1bc..48cd70a1d0e1f1bee0c86f76dedff484ace7d0c9 100644 --- a/src/lib_scoru_wasm/test/wasm_utils.ml +++ b/src/lib_scoru_wasm/test/wasm_utils.ml @@ -204,6 +204,16 @@ let make_module_inst list_key_vals src = Instance.update_module_ref module_reg module_key module_inst ; (module_reg, module_key, host_funcs_registry) +let retrieve_memory module_reg = + let open Lwt_syntax in + let* (module_inst : Instance.module_inst) = + Instance.ModuleMap.get "test" module_reg + in + let memories = module_inst.memories in + if Lazy_vector.Int32Vector.num_elements memories = 1l then + Lazy_vector.Int32Vector.get 0l memories + else assert false + module Kernels = struct (* Kernel failing at `kernel_next` invocation. *) let unreachable_kernel = "unreachable" @@ -257,13 +267,3 @@ let test_with_kernel kernel (test : string -> (unit, _) result Lwt.t) () = test kernel) in return_unit - -let retrieve_memory module_reg = - let open Lwt_syntax in - let* (module_inst : Instance.module_inst) = - Instance.ModuleMap.get "test" module_reg - in - let memories = module_inst.memories in - if Lazy_vector.Int32Vector.num_elements memories = 1l then - Lazy_vector.Int32Vector.get 0l memories - else assert false diff --git a/src/lib_tree_encoding/tezos_tree_encoding.mli b/src/lib_tree_encoding/tezos_tree_encoding.mli index 940a4590723d6a0c4e8fd151d5ecd7c839354f5d..f2f4fc01a63ddb6ba43d39ee22be21e8938c8421 100644 --- a/src/lib_tree_encoding/tezos_tree_encoding.mli +++ b/src/lib_tree_encoding/tezos_tree_encoding.mli @@ -272,6 +272,9 @@ module type TREE = sig val hash : tree -> Context_hash.t val length : tree -> key -> int Lwt.t + + val list : + tree -> ?offset:int -> ?length:int -> key -> (string * tree) list Lwt.t end type wrapped_tree diff --git a/src/lib_tree_encoding/tree.ml b/src/lib_tree_encoding/tree.ml index 4a110937032da9cbb9aa32bd36cfbcb13ee4bf5f..297c7c02b8d91d7b914c8da15bd7b41c1e44d93a 100644 --- a/src/lib_tree_encoding/tree.ml +++ b/src/lib_tree_encoding/tree.ml @@ -50,6 +50,9 @@ module type S = sig val hash : tree -> Context_hash.t val length : tree -> key -> int Lwt.t + + val list : + tree -> ?offset:int -> ?length:int -> key -> (string * tree) list Lwt.t end type 'tree backend = (module S with type tree = 'tree) @@ -83,6 +86,16 @@ let hash : type tree. tree backend -> tree -> Context_hash.t = let length : type tree. tree backend -> tree -> key -> int Lwt.t = fun (module T) tree key -> T.length tree key +let list : + type tree. + tree backend -> + tree -> + ?offset:int -> + ?length:int -> + key -> + (string * tree) list Lwt.t = + fun (module T) tree ?offset ?length key -> T.list tree ?offset ?length key + type wrapped_tree = Wrapped_tree : 'tree * 'tree backend -> wrapped_tree type Tezos_lazy_containers.Lazy_map.tree += Wrapped of wrapped_tree @@ -113,6 +126,11 @@ module Wrapped : S with type tree = wrapped_tree = struct let length (Wrapped_tree (t, b)) key = length b t key + let list (Wrapped_tree (t, b)) ?offset ?length key = + let open Lwt.Syntax in + let+ list = list b t ?offset ?length key in + List.map (fun (step, tree) -> (step, Wrapped_tree (tree, b))) list + let find (Wrapped_tree (t, b)) key = find b t key let hash (Wrapped_tree (t, b)) = hash b t diff --git a/src/lib_tree_encoding/tree.mli b/src/lib_tree_encoding/tree.mli index fc626fbdce031f700335ccca16cd9c2155db9abd..48873f112af472d1ba7f0a4ea589f7ef61261791 100644 --- a/src/lib_tree_encoding/tree.mli +++ b/src/lib_tree_encoding/tree.mli @@ -54,6 +54,9 @@ module type S = sig val hash : tree -> Context_hash.t val length : tree -> key -> int Lwt.t + + val list : + tree -> ?offset:int -> ?length:int -> key -> (string * tree) list Lwt.t end type 'tree backend = (module S with type tree = 'tree) @@ -74,6 +77,14 @@ val find_tree : 'tree backend -> 'tree -> key -> 'tree option Lwt.t val length : 'tree backend -> 'tree -> key -> int Lwt.t +val list : + 'tree backend -> + 'tree -> + ?offset:int -> + ?length:int -> + key -> + (string * 'tree) list Lwt.t + (** A [wrapped_tree] allows modifications to the underlying tree, without affecting the tree that it was decoded from. *) type wrapped_tree = Wrapped_tree : 'tree * 'tree backend -> wrapped_tree