diff --git a/packages/polywrap-client/polywrap_client/client.py b/packages/polywrap-client/polywrap_client/client.py index ddbdbe32..b34cdaa7 100644 --- a/packages/polywrap-client/polywrap_client/client.py +++ b/packages/polywrap-client/polywrap_client/client.py @@ -20,6 +20,7 @@ UriPackage, UriPackageOrWrapper, UriResolutionContext, + InterfaceImplementations, Wrapper, ) from polywrap_msgpack import msgpack_decode, msgpack_encode @@ -53,6 +54,17 @@ def get_uri_resolver( def get_envs(self, options: Optional[GetEnvsOptions] = None) -> List[Env]: return self._config.envs + def get_interfaces(self) -> List[InterfaceImplementations]: + return self._config.interfaces + + def get_implementations(self, uri: Uri) -> Result[List[Uri], Exception]: + if interface_implementations := next( + filter(lambda x: x.interface == uri, self._config.interfaces), None + ): + return Ok(interface_implementations.implementations) + else: + return Err(ValueError(f"Unable to find implementations for uri: {uri}")) + def get_env_by_uri( self, uri: Uri, options: Optional[GetEnvsOptions] = None ) -> Union[Env, None]: @@ -62,7 +74,9 @@ async def get_file(self, uri: Uri, options: GetFileOptions) -> Union[bytes, str] loaded_wrapper = (await self.load_wrapper(uri)).unwrap() return await loaded_wrapper.get_file(options) - async def get_manifest(self, uri: Uri, options: Optional[GetManifestOptions] = None) -> AnyWrapManifest: + async def get_manifest( + self, uri: Uri, options: Optional[GetManifestOptions] = None + ) -> AnyWrapManifest: loaded_wrapper = (await self.load_wrapper(uri)).unwrap() return loaded_wrapper.get_manifest() @@ -124,4 +138,5 @@ async def invoke(self, options: InvokerOptions) -> InvokeResult: return result except Exception as e: - return InvokeResult(result=None, error=e) + raise e + # return InvokeResult(result=None, error=e) diff --git a/packages/polywrap-client/tests/cases/simple-interface/implementation/wrap.info b/packages/polywrap-client/tests/cases/simple-interface/implementation/wrap.info new file mode 100644 index 00000000..afe55c11 Binary files /dev/null and b/packages/polywrap-client/tests/cases/simple-interface/implementation/wrap.info differ diff --git a/packages/polywrap-client/tests/cases/simple-interface/implementation/wrap.wasm b/packages/polywrap-client/tests/cases/simple-interface/implementation/wrap.wasm new file mode 100755 index 00000000..e8772878 Binary files /dev/null and b/packages/polywrap-client/tests/cases/simple-interface/implementation/wrap.wasm differ diff --git a/packages/polywrap-client/tests/cases/simple-interface/interface/wrap.info b/packages/polywrap-client/tests/cases/simple-interface/interface/wrap.info new file mode 100644 index 00000000..e7ea4d79 Binary files /dev/null and b/packages/polywrap-client/tests/cases/simple-interface/interface/wrap.info differ diff --git a/packages/polywrap-client/tests/cases/simple-interface/wrapper/wrap.info b/packages/polywrap-client/tests/cases/simple-interface/wrapper/wrap.info new file mode 100644 index 00000000..29b5baca Binary files /dev/null and b/packages/polywrap-client/tests/cases/simple-interface/wrapper/wrap.info differ diff --git a/packages/polywrap-client/tests/cases/simple-interface/wrapper/wrap.wasm b/packages/polywrap-client/tests/cases/simple-interface/wrapper/wrap.wasm new file mode 100755 index 00000000..0aca8ee9 Binary files /dev/null and b/packages/polywrap-client/tests/cases/simple-interface/wrapper/wrap.wasm differ diff --git a/packages/polywrap-client/tests/test_client.py b/packages/polywrap-client/tests/test_client.py index c2a9799c..a8157aa8 100644 --- a/packages/polywrap-client/tests/test_client.py +++ b/packages/polywrap-client/tests/test_client.py @@ -1,7 +1,7 @@ from pathlib import Path from polywrap_client import PolywrapClient -from polywrap_core import Uri, InvokerOptions +from polywrap_core import Uri, InvokerOptions, InterfaceImplementations from polywrap_uri_resolvers import BaseUriResolver, SimpleFileReader from polywrap_client.client import PolywrapClientConfig @@ -31,17 +31,43 @@ async def test_subinvoke(): }, ) - client = PolywrapClient(config=PolywrapClientConfig( - envs=[], resolver=uri_resolver - )) + client = PolywrapClient(config=PolywrapClientConfig(envs=[], resolver=uri_resolver)) uri = Uri( f'fs/{Path(__file__).parent.joinpath("cases", "simple-subinvoke", "invoke").absolute()}' ) - args = b'\x82\xa1a\x01\xa1b\x02' - options = InvokerOptions( - uri=uri, method="add", args=args, encode_result=False - ) + args = {"a": 1, "b": 2} + options = InvokerOptions(uri=uri, method="add", args=args, encode_result=False) result = await client.invoke(options) assert result.result == "1 + 2 = 3" + +async def test_interface_implementation(): + uri_resolver = BaseUriResolver( + file_reader=SimpleFileReader(), + redirects={}, + ) + + impl_uri = Uri( + f'fs/{Path(__file__).parent.joinpath("cases", "simple-interface", "implementation").absolute()}' + ) + + client = PolywrapClient( + config=PolywrapClientConfig( + envs=[], + resolver=uri_resolver, + interfaces=[ + InterfaceImplementations( + interface=Uri("ens/interface.eth"), implementations=[impl_uri] + ) + ], + ) + ) + uri = Uri( + f'fs/{Path(__file__).parent.joinpath("cases", "simple-interface", "wrapper").absolute()}' + ) + args = {"arg": {"str": "hello", "uint8": 2}} + options = InvokerOptions(uri=uri, method="moduleMethod", args=args, encode_result=False) + result = await client.invoke(options) + + assert result.result == {"str": "hello", "uint8": 2} diff --git a/packages/polywrap-core/polywrap_core/types/client.py b/packages/polywrap-core/polywrap_core/types/client.py index 754409b2..c84ad645 100644 --- a/packages/polywrap-core/polywrap_core/types/client.py +++ b/packages/polywrap-core/polywrap_core/types/client.py @@ -6,6 +6,7 @@ from polywrap_manifest import DeserializeManifestOptions, AnyWrapManifest +from .interface_implementation import InterfaceImplementations from .env import Env from .invoke import Invoker from .uri import Uri @@ -16,6 +17,7 @@ @dataclass(slots=True, kw_only=True) class ClientConfig: envs: List[Env] = field(default_factory=list) + interfaces: List[InterfaceImplementations] = field(default_factory=list) resolver: IUriResolver @@ -41,6 +43,10 @@ class GetManifestOptions(DeserializeManifestOptions): class Client(Invoker, UriResolverHandler): + @abstractmethod + def get_interfaces(self) -> List[InterfaceImplementations]: + pass + @abstractmethod def get_envs(self, options: Optional[GetEnvsOptions] = None) -> List[Env]: pass diff --git a/packages/polywrap-core/polywrap_core/types/interface_implementation.py b/packages/polywrap-core/polywrap_core/types/interface_implementation.py new file mode 100644 index 00000000..dbaf4a21 --- /dev/null +++ b/packages/polywrap-core/polywrap_core/types/interface_implementation.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass +from typing import List + +from .uri import Uri + +@dataclass(slots=True, kw_only=True) +class InterfaceImplementations: + interface: Uri + implementations: List[Uri] diff --git a/packages/polywrap-core/polywrap_core/types/invoke.py b/packages/polywrap-core/polywrap_core/types/invoke.py index b46d2cf6..3fde9a02 100644 --- a/packages/polywrap-core/polywrap_core/types/invoke.py +++ b/packages/polywrap-core/polywrap_core/types/invoke.py @@ -2,7 +2,9 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, List, Optional, Union + +from result import Result from .uri import Uri from .uri_resolution_context import IUriResolutionContext @@ -57,6 +59,10 @@ class Invoker(ABC): async def invoke(self, options: InvokerOptions) -> InvokeResult: pass + @abstractmethod + def get_implementations(self, uri: Uri) -> Result[List[Uri], Exception]: + pass + class Invocable(ABC): @abstractmethod diff --git a/packages/polywrap-wasm/polywrap_wasm/imports.py b/packages/polywrap-wasm/polywrap_wasm/imports.py index 9434f74c..b45dcc58 100644 --- a/packages/polywrap-wasm/polywrap_wasm/imports.py +++ b/packages/polywrap-wasm/polywrap_wasm/imports.py @@ -1,4 +1,6 @@ +from typing import List from polywrap_core import Invoker, InvokeResult, InvokerOptions, Uri +from polywrap_msgpack import msgpack_encode from unsync import Unfuture, unsync from wasmtime import ( FuncType, @@ -11,6 +13,7 @@ Store, ValType, ) +from result import Err from .buffer import read_bytes, read_string, write_bytes, write_string from .errors import WasmAbortError @@ -36,6 +39,18 @@ def create_instance( """ mem = Memory(store, MemoryType(Limits(1, None))) + wrap_debug_log_type = FuncType( + [ + ValType.i32(), + ValType.i32() + ], + [], + ) + + def wrap_debug_log(ptr: int, len: int) -> None: + msg = read_string(mem.data_ptr(store), mem.data_len(store), ptr, len) + print(msg) + wrap_abort_type = FuncType( [ ValType.i32(), @@ -116,6 +131,9 @@ def wrap_subinvoke( args_ptr: int, args_len: int, ) -> bool: + state.subinvoke["result"] = None + state.subinvoke["error"] = None + uri = read_string(mem.data_ptr(store), mem.data_len(store), uri_ptr, uri_len) method = read_string( mem.data_ptr(store), mem.data_len(store), method_ptr, method_len @@ -173,15 +191,149 @@ def wrap_subinvoke_error(ptr: int) -> None: mem.data_ptr(store), mem.data_len(store), state.subinvoke["error"], ptr ) + wrap_subinvoke_implementation_type = FuncType( + [ + ValType.i32(), + ValType.i32(), + ValType.i32(), + ValType.i32(), + ValType.i32(), + ValType.i32(), + ValType.i32(), + ValType.i32(), + ], + [ValType.i32()], + ) + + def wrap_subinvoke_implementation( + interface_uri_ptr: int, + interface_uri_len: int, + impl_uri_ptr: int, + impl_uri_len: int, + method_ptr: int, + method_len: int, + args_ptr: int, + args_len: int + ) -> bool: + state.subinvoke_implementation["result"] = None + state.subinvoke_implementation["error"] = None + + interface_uri = read_string(mem.data_ptr(store), mem.data_len(store), interface_uri_ptr, interface_uri_len) + impl_uri = read_string(mem.data_ptr(store), mem.data_len(store), impl_uri_ptr, impl_uri_len) + method = read_string( + mem.data_ptr(store), mem.data_len(store), method_ptr, method_len + ) + args = read_bytes(mem.data_ptr(store), mem.data_len(store), args_ptr, args_len) + + unfuture_result: Unfuture[InvokeResult] = unsync_invoke( + invoker, + InvokerOptions(uri=Uri(impl_uri), method=method, args=args, encode_result=True), + ) + result = unfuture_result.result() + + if result.result: + state.subinvoke_implementation["result"] = result.result + return True + elif result.error: + state.subinvoke_implementation["error"] = "".join(str(x) for x in result.error.args) + return False + else: + raise ValueError(f"interface implementation subinvoke failed for uri: {interface_uri}!") + + wrap_subinvoke_implementation_result_len_type = FuncType([], [ValType.i32()]) + + def wrap_subinvoke_implementation_result_len() -> int: + if not state.subinvoke_implementation["result"]: + raise WasmAbortError( + "__wrap_subinvoke_implementation_result_len: subinvoke_implementation.result is not set" + ) + return len(state.subinvoke_implementation["result"]) + + wrap_subinvoke_implementation_result_type = FuncType([ValType.i32()], []) + + def wrap_subinvoke_implementation_result(ptr: int) -> None: + if not state.subinvoke_implementation["result"]: + raise WasmAbortError("__wrap_subinvoke_implementation_result: subinvoke_implementation.result is not set") + write_bytes( + mem.data_ptr(store), mem.data_len(store), state.subinvoke_implementation["result"], ptr + ) + + wrap_subinvoke_implementation_error_len_type = FuncType([], [ValType.i32()]) + + def wrap_subinvoke_implementation_error_len() -> int: + if not state.subinvoke_implementation["error"]: + raise WasmAbortError( + "__wrap_subinvoke_implementation_error_len: subinvoke_implementation.error is not set" + ) + return len(state.subinvoke_implementation["error"]) + + wrap_subinvoke_implementation_error_type = FuncType([ValType.i32()], []) + + def wrap_subinvoke_implementation_error(ptr: int) -> None: + if not state.subinvoke_implementation["error"]: + raise WasmAbortError("__wrap_subinvoke_implementation_error: subinvoke_implementation.error is not set") + write_string( + mem.data_ptr(store), mem.data_len(store), state.subinvoke_implementation["error"], ptr + ) + + wrap_get_implementations_type = FuncType([ValType.i32(), ValType.i32()], [ValType.i32()]) + + def wrap_get_implementations(uri_ptr: int, uri_len: int) -> bool: + uri = read_string(mem.data_ptr(store), mem.data_len(store), uri_ptr, uri_len) + result = invoker.get_implementations(uri=Uri(uri)) + if isinstance(result, Err): + raise WasmAbortError(" ".join(result.unwrap_err().args)) + implementations: List[str] = [uri.uri for uri in result.unwrap()] + state.get_implementations_result = msgpack_encode(implementations) + return len(implementations) > 0 + + wrap_get_implementations_result_len_type = FuncType([], [ValType.i32()]) + + def wrap_get_implementations_result_len() -> int: + if not state.get_implementations_result: + raise WasmAbortError( + "__wrap_get_implementations_result_len: get_implementations_result is not set" + ) + return len(state.get_implementations_result) + + wrap_get_implementations_result_type = FuncType([ValType.i32()], []) + + def wrap_get_implementations_result(ptr: int) -> None: + if not state.get_implementations_result: + raise WasmAbortError("__wrap_get_implementations_result: get_implementations_result is not set") + write_bytes( + mem.data_ptr(store), mem.data_len(store), state.get_implementations_result, ptr + ) + + # TODO: use generics or any on wasmtime codebase to fix typings + linker.define_func("wrap", "__wrap_debug_log", wrap_debug_log_type, wrap_debug_log) # type: ignore partially unknown linker.define_func("wrap", "__wrap_abort", wrap_abort_type, wrap_abort) # type: ignore partially unknown + + # invoke linker.define_func("wrap", "__wrap_invoke_args", wrap_invoke_args_type, wrap_invoke_args) # type: ignore partially unknown linker.define_func("wrap", "__wrap_invoke_result", wrap_invoke_result_type, wrap_invoke_result) # type: ignore partially unknown linker.define_func("wrap", "__wrap_invoke_error", wrap_invoke_error_type, wrap_invoke_error) # type: ignore partially unknown + + # subinvoke linker.define_func("wrap", "__wrap_subinvoke", wrap_subinvoke_type, wrap_subinvoke) # type: ignore partially unknown linker.define_func("wrap", "__wrap_subinvoke_result_len", wrap_subinvoke_result_len_type, wrap_subinvoke_result_len) # type: ignore partially unknown linker.define_func("wrap", "__wrap_subinvoke_result", wrap_subinvoke_result_type, wrap_subinvoke_result) # type: ignore partially unknown linker.define_func("wrap", "__wrap_subinvoke_error_len", wrap_subinvoke_error_len_type, wrap_subinvoke_error_len) # type: ignore partially unknown linker.define_func("wrap", "__wrap_subinvoke_error", wrap_subinvoke_error_type, wrap_subinvoke_error) # type: ignore partially unknown + + # subinvoke implementation + linker.define_func("wrap", "__wrap_subinvokeImplementation", wrap_subinvoke_implementation_type, wrap_subinvoke_implementation) # type: ignore partially unknown + linker.define_func("wrap", "__wrap_subinvokeImplementation_result_len", wrap_subinvoke_implementation_result_len_type, wrap_subinvoke_implementation_result_len) # type: ignore partially unknown + linker.define_func("wrap", "__wrap_subinvokeImplementation_result", wrap_subinvoke_implementation_result_type, wrap_subinvoke_implementation_result) # type: ignore partially unknown + linker.define_func("wrap", "__wrap_subinvokeImplementation_error_len", wrap_subinvoke_implementation_error_len_type, wrap_subinvoke_implementation_error_len) # type: ignore partially unknown + linker.define_func("wrap", "__wrap_subinvokeImplementation_error", wrap_subinvoke_implementation_error_type, wrap_subinvoke_implementation_error) # type: ignore partially unknown + + # getImplementations + linker.define_func("wrap", "__wrap_getImplementations", wrap_get_implementations_type, wrap_get_implementations) # type: ignore partially unknown + linker.define_func("wrap", "__wrap_getImplementations_result_len", wrap_get_implementations_result_len_type, wrap_get_implementations_result_len) # type: ignore partially unknown + linker.define_func("wrap", "__wrap_getImplementations_result", wrap_get_implementations_result_type, wrap_get_implementations_result) # type: ignore partially unknown + + # memory linker.define("env", "memory", mem) return linker.instantiate(store, module) diff --git a/packages/polywrap-wasm/polywrap_wasm/types/state.py b/packages/polywrap-wasm/polywrap_wasm/types/state.py index 8d7c0afe..8892569c 100644 --- a/packages/polywrap-wasm/polywrap_wasm/types/state.py +++ b/packages/polywrap-wasm/polywrap_wasm/types/state.py @@ -30,6 +30,7 @@ class State: subinvoke_implementation: RawSubinvokeImplementationResult = field( default_factory=lambda: {"result": None, "error": None, "args": []} ) + get_implementations_result: Optional[bytes] = None method: Optional[str] = None args: Optional[bytes] = None env: Optional[bytes] = None diff --git a/packages/polywrap-wasm/tests/test_wasm_wrapper.py b/packages/polywrap-wasm/tests/test_wasm_wrapper.py index 81fad29c..ce0d6d3e 100644 --- a/packages/polywrap-wasm/tests/test_wasm_wrapper.py +++ b/packages/polywrap-wasm/tests/test_wasm_wrapper.py @@ -1,3 +1,4 @@ +from typing import List import pytest from pathlib import Path @@ -9,11 +10,17 @@ from polywrap_wasm.constants import WRAP_MANIFEST_PATH +from result import Result, Err + @pytest.fixture def mock_invoker(): class MockInvoker(Invoker): async def invoke(self, options: InvokerOptions) -> InvokeResult: return InvokeResult() + + def get_implementations(self, uri: Uri) -> Result[List[Uri], Exception]: + return Err(NotImplementedError()) + return MockInvoker()