diff --git a/src/lib_scoru_wasm/fast/funcs.ml b/src/lib_scoru_wasm/fast/funcs.ml index a10b46c8ece865cbeecafa337da55d926edfda2a..7c2468dd4acb04834d7a994171c5d13f6b1a6595 100644 --- a/src/lib_scoru_wasm/fast/funcs.ml +++ b/src/lib_scoru_wasm/fast/funcs.ml @@ -197,6 +197,19 @@ let make ~version ~reveal_builtins ~write_debug state = ~dst ~max_size) in + let store_get_hash = + fn + (i32 @-> i32 @-> i32 @-> i32 @-> returning1 i32) + (fun key_offset key_length dst max_size -> + with_mem @@ fun memory -> + Host_funcs.Aux.store_get_hash + ~durable:state.durable + ~memory + ~key_offset + ~key_length + ~dst + ~max_size) + in let store_value_size = fn (i32 @-> i32 @-> returning1 i32) @@ -262,7 +275,11 @@ let make ~version ~reveal_builtins ~write_debug state = ("reveal_metadata", reveal_metadata); ] in - let extra = match version with Wasm_pvm_state.V0 | V1 -> [] in + let extra = + match version with + | Wasm_pvm_state.V0 -> [] + | V1 -> [("__internal_store_get_hash", store_get_hash)] + in List.map (fun (name, impl) -> (Constants.wasm_host_funcs_virual_module, name, impl)) (base @ extra) diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 0d37a4526f70004524b75a9d2aa91a52162e6d91..66bc2123ec0ff57117ed1fb01fcef21c51daa971 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -239,6 +239,15 @@ module Aux = struct max_size:int32 -> int32 Lwt.t + val store_get_hash : + durable:Durable.t -> + memory:memory -> + key_offset:int32 -> + key_length:int32 -> + dst:int32 -> + max_size:int32 -> + int32 Lwt.t + val reveal : memory:memory -> dst:int32 -> @@ -559,6 +568,29 @@ module Aux = struct in extract_error_code res + let store_get_hash ~durable ~memory ~key_offset ~key_length ~dst ~max_size = + let open Lwt_result_syntax in + let*! res = + let* key = load_key_from_memory key_offset key_length memory in + let* result = + guard (fun () -> Durable.hash_exn ~kind:`Subtree durable key) + in + let result = + Data_encoding.Binary.to_string_exn Context_hash.encoding result + 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 M.store_bytes memory dst result else return_unit + in + Int32.of_int @@ String.length result + in + extract_error_code res + let reveal ~memory ~dst ~max_bytes ~payload = let open Lwt_syntax in let* res = @@ -896,6 +928,46 @@ let store_get_nth_key = (durable, [value result], store_get_nth_key_ticks key_length result) | _ -> raise Bad_input) +let store_get_hash_name = "tezos_internal_store_get_hash" + +let store_get_hash_type = + let input_types = + Types.[NumType I32Type; NumType I32Type; 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_hash_ticks key_size result = + Tick_model.( + with_error result (fun () -> read_key_in_memory key_size + tree_access) + |> to_z) + +let store_get_hash = + 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 (I32 dst); + Num (I32 max_size); + ] -> + let* memory = retrieve_memory memories in + let+ result = + Aux.store_get_hash + ~durable:(Durable.of_storage_exn durable) + ~memory + ~key_offset + ~key_length + ~dst + ~max_size + in + (durable, [value result], store_get_hash_ticks key_length result) + | _ -> raise Bad_input) + let store_copy_name = "tezos_store_copy" let store_copy_type = @@ -1142,7 +1214,7 @@ let store_write = store_write_ticks key_length num_bytes code ) | _ -> raise Bad_input) -let lookup_opt ~version:_ name = +let lookup_opt ~version name = match name with | "read_input" -> Some (ExternFunc (HostFunc (read_input_type, read_input_name))) @@ -1173,6 +1245,12 @@ let lookup_opt ~version:_ name = Some (ExternFunc (HostFunc (store_read_type, store_read_name))) | "store_write" -> Some (ExternFunc (HostFunc (store_write_type, store_write_name))) + | "__internal_store_get_hash" -> ( + match version with + | Wasm_pvm_state.V0 -> None + | V1 -> + Some + (ExternFunc (HostFunc (store_get_hash_type, store_get_hash_name)))) | _ -> None let lookup ~version name = @@ -1209,7 +1287,13 @@ let all_V0 = Host_funcs.(base |> with_write_debug ~write_debug:Noop |> construct) let all_V1 = - Host_funcs.(base |> with_write_debug ~write_debug:Noop |> construct) + Host_funcs.( + base + |> with_write_debug ~write_debug:Noop + |> with_host_function + ~global_name:store_get_hash_name + ~implem:store_get_hash + |> construct) let all ~version = match version with Wasm_pvm_state.V0 -> all_V0 | Wasm_pvm_state.V1 -> all_V1 @@ -1218,8 +1302,16 @@ let all ~version = each initialization tick. *) let all_debug ~version ~write_debug = match version with - | Wasm_pvm_state.V0 | Wasm_pvm_state.V1 -> + | Wasm_pvm_state.V0 -> Host_funcs.(base |> with_write_debug ~write_debug |> construct) + | V1 -> + Host_funcs.( + base + |> with_write_debug ~write_debug + |> with_host_function + ~global_name:store_get_hash_name + ~implem:store_get_hash + |> construct) module Internal_for_tests = struct let metadata_size = Int32.to_int metadata_size @@ -1249,5 +1341,7 @@ module Internal_for_tests = struct let store_get_nth_key = Func.HostFunc (store_get_nth_key_type, store_get_nth_key_name) + let store_get_hash = Func.HostFunc (store_get_hash_type, store_get_hash_name) + let write_debug = Func.HostFunc (write_debug_type, write_debug_name) end diff --git a/src/lib_scoru_wasm/host_funcs.mli b/src/lib_scoru_wasm/host_funcs.mli index 75f92d39c26251a8eec9dfdc34a8869fc7128859..3cbfe40087cddcc52d2d0a7aa2c29093f95f5479 100644 --- a/src/lib_scoru_wasm/host_funcs.mli +++ b/src/lib_scoru_wasm/host_funcs.mli @@ -233,6 +233,15 @@ module Aux : sig max_size:int32 -> int32 Lwt.t + val store_get_hash : + durable:Durable.t -> + memory:memory -> + key_offset:int32 -> + key_length:int32 -> + dst:int32 -> + max_size:int32 -> + int32 Lwt.t + (** [reveal mem base size payload] is intended to be the function used by the PVM to load at most [size] bytes of the result [payload] of a reveal step in [mem], at address [base] *) @@ -338,5 +347,7 @@ module Internal_for_tests : sig val store_get_nth_key : Tezos_webassembly_interpreter.Instance.func_inst + val store_get_hash : Tezos_webassembly_interpreter.Instance.func_inst + val write_debug : 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 bfc0071fea5c9b40cd620a9f9f41b8bb69156de1..4be67d0e805a2c14868d0c75a9f83aa40dd18130 100644 --- a/src/lib_scoru_wasm/test/test_durable_storage.ml +++ b/src/lib_scoru_wasm/test/test_durable_storage.ml @@ -317,6 +317,77 @@ let test_store_get_nth_key ~version () = assert (truncated_string_at_two = expected_truncated_string_at_two) ; Lwt.return_ok () +let test_store_get_hash ~version () = + match version with + | Wasm_pvm_state.V0 -> + (* the [store_get_store_hash] host function is not available before [V1]. *) + Lwt.return_ok () + | V1 -> + let open Lwt_syntax in + let* durable_storage = make_durable [("/foo/bar", "/foobar")] in + let src = 20l in + let key = "/foo" in + let key_len = Int32.of_int @@ String.length key in + let dst = Int32.(add src key_len) in + let module_reg, module_key, host_funcs_registry = + make_module_inst ~version [key] src + in + let call_host_func values = + Eval.invoke + ~module_reg + ~caller:module_key + ~durable:durable_storage + host_funcs_registry + Host_funcs.Internal_for_tests.store_get_hash + values + in + (* Test valid access *) + let values = + Values.[Num (I32 src); Num (I32 key_len); Num (I32 dst); Num (I32 32l)] + in + let* _durable, _result = call_host_func values in + let* mem = retrieve_memory module_reg in + let* hash = Memory.load_bytes mem dst 32 in + let durable = Durable.of_storage_exn durable_storage in + let* hash_expected = + Durable.hash_exn ~kind:`Subtree durable Durable.(key_of_string_exn key) + in + let hash_expected = Context_hash.to_string hash_expected in + assert (hash = hash_expected) ; + (* Test wrong access for keys *) + let values = + Values. + [ + (* We allocate 20 pages of 64KiB in [make_module_inst], so this + value is out of the bound of the memory. *) + Num (I32 Int32.(mul 21l 0x10000l)); + Num (I32 key_len); + Num (I32 dst); + Num (I32 32l); + ] + in + let* _durable, result = call_host_func values in + assert ( + result + = Values.[Num (I32 (Host_funcs.Error.code Memory_invalid_access))]) ; + (* Test wrong access for result *) + let values = + Values. + [ + Num (I32 20l); + Num (I32 key_len); + (* We allocate 20 pages of 64KiB in [make_module_inst], so this + value is out of the bound of the memory. *) + Num (I32 Int32.(mul 21l 0x10000l)); + Num (I32 32l); + ] + in + let* _durable, result = call_host_func values in + assert ( + result + = Values.[Num (I32 (Host_funcs.Error.code Memory_invalid_access))]) ; + Lwt.return_ok () + (* Test checking that [store_delete key] deletes the subtree at [key] from the durable storage. *) let test_store_delete ~version () = @@ -1039,6 +1110,7 @@ let tests = ("store_read on non-value", `Quick, test_store_read_non_value); ("store_write", `Quick, test_store_write); ("store_value_size", `Quick, test_store_value_size); + ("store_get_hash", `Quick, test_store_get_hash); ] @ [ tztest "Durable: find value" `Quick test_durable_find_value; diff --git a/src/lib_scoru_wasm/test/test_wasm_pvm.ml b/src/lib_scoru_wasm/test/test_wasm_pvm.ml index 0f5eca80bdee9559c1f559056ba91a6d59e72bc2..0bf3c25130d106908a80423fa66d4c14496b063a 100644 --- a/src/lib_scoru_wasm/test/test_wasm_pvm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_pvm.ml @@ -448,6 +448,33 @@ let should_run_store_copy_kernel ~version () = let* () = assert_store_value tree "/a/b" (Some "ab") in return_ok_unit +let nop_store_get_hash_module = + {| +(module + (import "smart_rollup_core" "__internal_store_get_hash" + (func $store_get_hash_key (param i32 i32 i32 i32) (result i32))) + (memory 1) + (export "mem" (memory 0)) + (func (export "kernel_run") + (nop))) + |} + +let try_run_store_get_hash ~version () = + let open Lwt_syntax in + let* tree = + initial_tree ~version ~from_binary:false nop_store_get_hash_module + in + let* tree = set_empty_inbox_step 0l tree in + let* tree = eval_until_input_or_reveal_requested tree in + let* state = Wasm.Internal_for_tests.get_tick_state tree in + let predicate state = + match version with + | Wasm_pvm_state.V0 -> is_stuck state + | V1 -> not (is_stuck state) + in + assert (predicate state) ; + Lwt_result_syntax.return_unit + let test_modify_read_only_storage_kernel ~version () = let open Lwt_syntax in let module_ = @@ -1674,6 +1701,9 @@ let tests = tztests_with_pvm ~versions:[V0; V1] [ + ( "Test __internal_store_get_hash available", + `Quick, + try_run_store_get_hash ); ( "Test unreachable kernel (tick per tick)", `Quick, fun ~version ->