From ed7b8dce39032253b3152afec6a16ecf2645a223 Mon Sep 17 00:00:00 2001 From: Corneliu Hoffman Date: Mon, 26 Sep 2022 09:18:36 +0100 Subject: [PATCH 1/4] SCORU/WASM: added the copy HostFunction- no tests yet --- src/lib_scoru_wasm/durable.ml | 9 +++++++ src/lib_scoru_wasm/durable.mli | 10 ++++++++ src/lib_scoru_wasm/host_funcs.ml | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/src/lib_scoru_wasm/durable.ml b/src/lib_scoru_wasm/durable.ml index 2ef564c47f55..65c168208f75 100644 --- a/src/lib_scoru_wasm/durable.ml +++ b/src/lib_scoru_wasm/durable.ml @@ -97,6 +97,13 @@ let find_value_exn tree key = | None -> raise Not_found | Some subtree -> Runner.decode E.chunked_byte_vector subtree +let find_tree tree key = T.find_tree tree @@ to_value_key key + +let find_tree_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 -> Lwt.return subtree + let count_subtrees tree key = let open Lwt.Syntax in let* opt = T.find_tree tree @@ to_value_key key in @@ -105,6 +112,8 @@ let count_subtrees tree key = let delete tree key = T.remove tree key +let add_tree = T.add_tree + let hash_exn 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 fc1cff654e58..0257e2511a4c 100644 --- a/src/lib_scoru_wasm/durable.mli +++ b/src/lib_scoru_wasm/durable.mli @@ -67,12 +67,22 @@ 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 +(** [find_tree durable key] optionally looks for the tree encoded at [key] + in [durable]. *) +val find_tree : t -> key -> t option Lwt.t + +(** raise @Not_found *) +val find_tree_exn : t -> key -> 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 +(** [add durable key subtree] adds [subtree] to [durable] at [key]. *) +val add_tree : t -> key -> t -> t Lwt.t + (** [hash_exn durable key] retrieves the tree hash of the value at the given [key]. This is not the same as the hash of the value. diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 06f50edc1de3..74aff534a564 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -301,6 +301,47 @@ 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 = + Host_funcs.Host_func + (fun _input_buffer _output_buffer durable memories inputs -> + let open Lwt.Syntax in + 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)); + ] -> + 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* move_tree = Durable.find_tree_exn tree from_key in + let+ tree = Durable.add_tree tree to_key move_tree in + (Durable.to_storage tree, []) + | _ -> raise Bad_input) + let lookup_opt name = match name with | "read_input" -> @@ -314,6 +355,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 +374,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 -- GitLab From 0cdb20e8eda1ba11e9f90f8ec77ffabc796bd75f Mon Sep 17 00:00:00 2001 From: Corneliu Hoffman Date: Mon, 26 Sep 2022 10:39:51 +0100 Subject: [PATCH 2/4] SCORU/WASM: added a test --- src/lib_scoru_wasm/host_funcs.ml | 3 +- src/lib_scoru_wasm/host_funcs.mli | 2 + .../test/test_durable_storage.ml | 87 ++++++++++++++++++- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 74aff534a564..6d36b1c677b3 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -324,7 +324,6 @@ let store_copy = ] -> 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 @@ -390,6 +389,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 7e3b96fa9650..b5eec35e9679 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 0d4883a85cbd..3e5bfa60bf62 100644 --- a/src/lib_scoru_wasm/test/test_durable_storage.ml +++ b/src/lib_scoru_wasm/test/test_durable_storage.ml @@ -72,7 +72,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 +448,91 @@ let test_durable_count_subtrees () = let* () = assert_subtree_count tree 0 "/bye" in Lwt.return_ok () +(* Test checking that [store_copy key] deletes the subtree at [key] from the + durable storage. *) +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/_ = "..." + /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 from_key = "/a/short/path/one" in + let to_key = "/a/long/path/one" 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* from_tree_opt = + Durable.find_tree durable @@ Durable.key_of_string_exn to_key + in + let* to_tree_opt = + Durable.find_tree durable @@ Durable.key_of_string_exn to_key + in + assert (from_tree_opt = to_tree_opt) ; + Lwt.return_ok () + (* Test invalid key encodings are rejected. *) let test_durable_invalid_keys () = let open Lwt.Syntax in @@ -481,6 +565,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; -- GitLab From e9c9174d1fcfe4d0f4529804f94590df5b08dd66 Mon Sep 17 00:00:00 2001 From: Corneliu Hoffman Date: Mon, 26 Sep 2022 18:35:16 +0100 Subject: [PATCH 3/4] SCORU/WASM: cleaned up the durable.ml to respond to comments --- src/lib_scoru_wasm/durable.ml | 12 +++++++----- src/lib_scoru_wasm/durable.mli | 12 +++--------- src/lib_scoru_wasm/host_funcs.ml | 3 +-- src/lib_scoru_wasm/test/test_durable_storage.ml | 14 ++++++++------ 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/lib_scoru_wasm/durable.ml b/src/lib_scoru_wasm/durable.ml index 65c168208f75..511fbb46966c 100644 --- a/src/lib_scoru_wasm/durable.ml +++ b/src/lib_scoru_wasm/durable.ml @@ -97,13 +97,17 @@ let find_value_exn tree key = | None -> raise Not_found | Some subtree -> Runner.decode E.chunked_byte_vector subtree -let find_tree tree key = T.find_tree tree @@ to_value_key key - let find_tree_exn tree key = let open Lwt.Syntax in - let* opt = T.find_tree tree @@ to_value_key key 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 + let+ tree = T.add_tree tree to_key move_tree in + tree + let count_subtrees tree key = let open Lwt.Syntax in let* opt = T.find_tree tree @@ to_value_key key in @@ -112,8 +116,6 @@ let count_subtrees tree key = let delete tree key = T.remove tree key -let add_tree = T.add_tree - let hash_exn 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 0257e2511a4c..c746aac29948 100644 --- a/src/lib_scoru_wasm/durable.mli +++ b/src/lib_scoru_wasm/durable.mli @@ -67,12 +67,9 @@ 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 -(** [find_tree durable key] optionally looks for the tree encoded at [key] - in [durable]. *) -val find_tree : t -> key -> t option Lwt.t - -(** raise @Not_found *) -val find_tree_exn : t -> key -> 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 @@ -80,9 +77,6 @@ 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 -(** [add durable key subtree] adds [subtree] to [durable] at [key]. *) -val add_tree : t -> key -> t -> t Lwt.t - (** [hash_exn durable key] retrieves the tree hash of the value at the given [key]. This is not the same as the hash of the value. diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 6d36b1c677b3..f87316892961 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -336,8 +336,7 @@ let store_copy = 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* move_tree = Durable.find_tree_exn tree from_key in - let+ tree = Durable.add_tree tree to_key move_tree in + let+ tree = Durable.copy_tree_exn tree from_key to_key in (Durable.to_storage tree, []) | _ -> raise Bad_input) diff --git a/src/lib_scoru_wasm/test/test_durable_storage.ml b/src/lib_scoru_wasm/test/test_durable_storage.ml index 3e5bfa60bf62..4155ef1b3430 100644 --- a/src/lib_scoru_wasm/test/test_durable_storage.ml +++ b/src/lib_scoru_wasm/test/test_durable_storage.ml @@ -462,7 +462,6 @@ let test_store_copy () = 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 @@ -524,13 +523,16 @@ let test_store_copy () = in assert (result = []) ; let durable = Durable.of_storage_exn durable in - let* from_tree_opt = - Durable.find_tree durable @@ Durable.key_of_string_exn to_key + let* from_tree = + Durable.find_value_exn durable @@ Durable.key_of_string_exn from_key in - let* to_tree_opt = - Durable.find_tree durable @@ Durable.key_of_string_exn to_key + let* to_tree = + Durable.find_value_exn durable @@ Durable.key_of_string_exn to_key in - assert (from_tree_opt = to_tree_opt) ; + + assert ( + Chunked_byte_vector.to_string from_tree + = Chunked_byte_vector.to_string to_tree) ; Lwt.return_ok () (* Test invalid key encodings are rejected. *) -- GitLab From 1f07bc39138731964c1c32064500af138674a563 Mon Sep 17 00:00:00 2001 From: Corneliu Hoffman Date: Tue, 27 Sep 2022 13:47:30 +0100 Subject: [PATCH 4/4] SCORU/WASM: added some assertions in the test respond to comments --- src/lib_scoru_wasm/durable.ml | 4 +- src/lib_scoru_wasm/host_funcs.ml | 42 +++++++++------- .../test/test_durable_storage.ml | 48 +++++++++++++------ 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/lib_scoru_wasm/durable.ml b/src/lib_scoru_wasm/durable.ml index 511fbb46966c..a1a85f264b84 100644 --- a/src/lib_scoru_wasm/durable.ml +++ b/src/lib_scoru_wasm/durable.ml @@ -97,6 +97,7 @@ 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 @@ -105,8 +106,7 @@ let find_tree_exn tree key = let copy_tree_exn tree from_key to_key = let open Lwt.Syntax in let* move_tree = find_tree_exn tree from_key in - let+ tree = T.add_tree tree to_key move_tree in - tree + T.add_tree tree to_key move_tree let count_subtrees tree key = let open Lwt.Syntax in diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index f87316892961..a4bf310aed92 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -311,10 +311,27 @@ let store_copy_type = 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 -> - let open Lwt.Syntax in match inputs with | [ Values.(Num (I32 from_key_offset)); @@ -322,22 +339,13 @@ let store_copy = Values.(Num (I32 to_key_offset)); Values.(Num (I32 to_key_length)); ] -> - 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, []) + store_copy_aux + durable + memories + from_key_offset + from_key_length + to_key_offset + to_key_length | _ -> raise Bad_input) let lookup_opt name = diff --git a/src/lib_scoru_wasm/test/test_durable_storage.ml b/src/lib_scoru_wasm/test/test_durable_storage.ml index 4155ef1b3430..038ac15a81a4 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 = @@ -448,19 +454,18 @@ let test_durable_count_subtrees () = let* () = assert_subtree_count tree 0 "/bye" in Lwt.return_ok () -(* Test checking that [store_copy key] deletes the subtree at [key] from the - durable storage. *) +(* 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/_ = "..." - /durable/a/short/path/one/_ = "..." - /durable/a/long/path/_ = "..." - - We expect that deleting "/a/short/path" is leaves only "/durable/a/long/path". + /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 = @@ -482,16 +487,26 @@ let test_store_copy () = let* tree = Tree_encoding_runner.encode (Tree_encoding.scope - ["durable"; "a"; "long"; "path"; "_"] + ["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/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 @@ -523,16 +538,19 @@ let test_store_copy () = in assert (result = []) ; let durable = Durable.of_storage_exn durable in - let* from_tree = + + 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* to_tree = + let* new_value_to_key = Durable.find_value_exn durable @@ Durable.key_of_string_exn to_key in - - assert ( - Chunked_byte_vector.to_string from_tree - = Chunked_byte_vector.to_string to_tree) ; + 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. *) -- GitLab