diff --git a/doc/source/graphql_serial_exc.rst b/doc/source/graphql_serial_exc.rst index 7da02a4..7a6f914 100644 --- a/doc/source/graphql_serial_exc.rst +++ b/doc/source/graphql_serial_exc.rst @@ -15,11 +15,30 @@ the gas object used to pay for the transaction. Note that all available gas objects will be smashed to one gas object during transaction build processing. -Here is a simple example: +Here is a simple sunny day example: .. code-block:: python :linenos: + import logging + + logger = logging.getLogger() + logging.basicConfig( + filename="ser_txn.log", + filemode="w", + encoding="utf-8", + format="%(asctime)s %(module)s %(levelname)s %(message)s", + level=logging.INFO, + ) + + from pysui import PysuiConfiguration, AsyncGqlClient + from pysui.sui.sui_pgql.execute.caching_txn import CachingTransaction + from pysui.sui.sui_pgql.execute.serial_exec import ( + SerialTransactionExecutor, + ser_txn_exc_logger, + ) + + async def test_splits(): """.""" client = AsyncGqlClient( @@ -31,6 +50,8 @@ Here is a simple example: ser_txne = SerialTransactionExecutor( client=client, signer=client.config.active_address, default_gas_budget=5000000 ) + # Set logging level + ser_txn_exc_logger.setLevel(logging.DEBUG) # Tx 1 ser_txn_1: CachingTransaction = await ser_txne.new_transaction() scoin = await ser_txn_1.split_coin(coin=ser_txn_1.gas, amounts=[1000000000]) @@ -53,5 +74,34 @@ In this example the two transaction times are, respectivley: .. code-block:: shell :linenos: - serial_exec DEBUG tx execution 0.783149003982544 + serial_exec DEBUG tx execution 0.9674050807952881 serial_exec DEBUG tx execution 0.1889028549194336 + +Note that the first transaction smashes and primes the gas object as well as resolving unresolved objects + +Exceptions +---------- + +Use caution when evaluating the result of the execution. If the result is not an instance of ExecutionResultGQL then +it is some error returned from either building, signing or executing the transaction. So, if the result is a +tuple (vs. an instance of ExecutionResultGQL) then it contains: + +`(ExecutorError,Exception)` + + +.. code-block:: python + :linenos: + + from pysui.sui.sui_pgql.pgql_types import ExecutionResultGQL + + async def test_splits(): + """.""" + # All code omitted + gx = await ser_txne.execute_transactions([ser_txn_1, ser_txn_2]) + + for gres in gx: + if not isinstance(gres,tuple): + print(gres.to_json(indent=2)) + else: + error_enum, exception = gres + print(f"Type error: {error_enum.name} exception: {exception}") diff --git a/pysui/sui/sui_pgql/execute/caching_exec.py b/pysui/sui/sui_pgql/execute/caching_exec.py index 0d8a071..80da83f 100644 --- a/pysui/sui/sui_pgql/execute/caching_exec.py +++ b/pysui/sui/sui_pgql/execute/caching_exec.py @@ -120,7 +120,7 @@ async def _smash_gas( # If noting available, throw exception if not coin_list: logger.debug("_smash_gas has no available coins") - raise TypeError( + raise ValueError( f"Signer {txn.signer_block.payer_address} has no available gas coins" ) # If one return it @@ -272,10 +272,7 @@ async def build_transaction( ) async def execute_transaction( - self, - txn_str: str, - txn_sigs: list[str], - **kwargs, + self, txn_str: str, txn_sigs: list[str] ) -> ptypes.ExecutionResultGQL: """.""" result = await self._client.execute_query_node( diff --git a/pysui/sui/sui_pgql/execute/caching_txn.py b/pysui/sui/sui_pgql/execute/caching_txn.py index 598900f..edd8824 100644 --- a/pysui/sui/sui_pgql/execute/caching_txn.py +++ b/pysui/sui/sui_pgql/execute/caching_txn.py @@ -101,12 +101,6 @@ async def _function_meta_args( ) raise ValueError(f"Unresolvable target {target}") - async def target_function_summary( - self, target: str - ) -> tuple[bcs.Address, str, str, int, pgql_type.MoveArgSummary]: - """Returns the argument summary of a target sui move function.""" - return await self._function_meta_args(target) - def _build_txn_data( self, gas_budget: int, @@ -182,39 +176,39 @@ async def build( ) return base64.b64encode(txn_data.serialize()).decode() - async def build_and_sign( - self, - *, - gas_budget: Optional[str] = None, - use_gas_objects: Optional[list[Union[str, pgql_type.SuiCoinObjectGQL]]] = None, - txn_expires_after: Optional[int] = None, - ) -> dict: - """build After creating the BCS TransactionKind, serialize to base64 string, create signatures and return. - - :param gas_budget: Specify the amount of gas for the transaction budget, defaults to None - :type gas_budget: Optional[str], optional - :param use_gas_objects: Specify gas object(s) (by ID or SuiCoinObjectGQL), defaults to None - :type use_gas_objects: Optional[list[Union[str, pgql_type.SuiCoinObjectGQL]]], optional - :param txn_expires_after: Specify the transaction expiration epoch ID, defaults to None - :type txn_expires_after: Optional[int],optional - :return: Dict of - { - "tx_bytestr": base64 encoded transaction bytes, - "sig_array": array of base64 encoded signature bytes - - } - :rtype: dict[str, str] - """ - txn_kind = await self.transaction_data( - gas_budget=gas_budget, - use_gas_objects=use_gas_objects, - txn_expires_after=txn_expires_after, - ) - tx_bytes = base64.b64encode(txn_kind.serialize()).decode() - sigs = self.signer_block.get_signatures( - config=self.client.config, tx_bytes=tx_bytes - ) - return {self._BUILD_BYTE_STR: tx_bytes, self._SIG_ARRAY: sigs} + # async def build_and_sign( + # self, + # *, + # gas_budget: Optional[str] = None, + # use_gas_objects: Optional[list[Union[str, pgql_type.SuiCoinObjectGQL]]] = None, + # txn_expires_after: Optional[int] = None, + # ) -> dict: + # """build After creating the BCS TransactionKind, serialize to base64 string, create signatures and return. + + # :param gas_budget: Specify the amount of gas for the transaction budget, defaults to None + # :type gas_budget: Optional[str], optional + # :param use_gas_objects: Specify gas object(s) (by ID or SuiCoinObjectGQL), defaults to None + # :type use_gas_objects: Optional[list[Union[str, pgql_type.SuiCoinObjectGQL]]], optional + # :param txn_expires_after: Specify the transaction expiration epoch ID, defaults to None + # :type txn_expires_after: Optional[int],optional + # :return: Dict of + # { + # "tx_bytestr": base64 encoded transaction bytes, + # "sig_array": array of base64 encoded signature bytes + + # } + # :rtype: dict[str, str] + # """ + # txn_kind = await self.transaction_data( + # gas_budget=gas_budget, + # use_gas_objects=use_gas_objects, + # txn_expires_after=txn_expires_after, + # ) + # tx_bytes = base64.b64encode(txn_kind.serialize()).decode() + # sigs = self.signer_block.get_signatures( + # config=self.client.config, tx_bytes=tx_bytes + # ) + # return {self._BUILD_BYTE_STR: tx_bytes, self._SIG_ARRAY: sigs} async def split_coin( self, diff --git a/pysui/sui/sui_pgql/execute/serial_exec.py b/pysui/sui/sui_pgql/execute/serial_exec.py index da31ed3..bf0e036 100644 --- a/pysui/sui/sui_pgql/execute/serial_exec.py +++ b/pysui/sui/sui_pgql/execute/serial_exec.py @@ -9,6 +9,7 @@ import base64 import logging import time +from enum import IntEnum from typing import Any, Coroutine, Optional, Union ser_txn_exc_logger = logging.getLogger("serial_exec") @@ -23,6 +24,12 @@ from .caching_txn import CachingTransaction +class ExecutorError(IntEnum): + BUILDING_ERROR = 1 + SIGNING_ERROR = 2 + EXECUTING_ERROR = 3 + + def _get_gascoin_from_effects(effects: bcst.TransactionEffects) -> bcs.ObjectReference: """.""" if effects.enum_name != "V2": @@ -149,8 +156,7 @@ def _sign_transaction(self, tx_str: str) -> list[str]: async def execute_transactions( self, transactions: list[CachingTransaction], - **kwargs, - ) -> list[ptypes.ExecutionResultGQL]: + ) -> list[Union[ptypes.ExecutionResultGQL, Exception]]: """Serially execute one or more transactions :param transactions: The transactions to execute @@ -164,24 +170,35 @@ async def execute_transactions( for tx in transactions: start_time = time.time() ser_txn_exc_logger.debug("Building transaction") - tx_str = await self._build_transaction(tx) - ser_txn_exc_logger.debug(f"Signing {tx_str}") + # Buillding is non-recoverable + tx_str = await asyncio.gather( + self._build_transaction(tx), return_exceptions=True + ) + if not isinstance(tx_str[0], str): + ser_txn_exc_logger.critical(f"tx build {tx_str[0].args}") + exe_res.append((ExecutorError.BUILDING_ERROR, tx_str[0])) + continue + tx_str: str = tx_str[0] + ser_txn_exc_logger.debug(f"Signing {tx_str}") try: ser_txn_exc_logger.debug("Cache transaction execution") # Sign the transaction sig_list = self._sign_transaction(tx_str) - results: ptypes.ExecutionResultGQL = ( - await self._cache.execute_transaction(tx_str, sig_list, **kwargs) + results = await asyncio.gather( + self._cache.execute_transaction(tx_str, sig_list) ) + if not isinstance(results[0], ptypes.ExecutionResultGQL): + ser_txn_exc_logger.critical(f"tx execution {results[0].args}") + exe_res.append((ExecutorError.EXECUTING_ERROR, results[0])) + continue + results: ptypes.ExecutionResultGQL = results[0] await self.apply_effects(results.bcs) end_time = time.time() - ser_txn_exc_logger.info(f"tx execution {end_time-start_time}") + ser_txn_exc_logger.info(f"tx execution time {end_time-start_time}") exe_res.append(results) - - except ValueError as exc: - ser_txn_exc_logger.debug("Error callingi cache execute") - await self.reset_cache() - raise exc + except ValueError as ve: + ser_txn_exc_logger.critical(f"tx signing non-recoverable {ve.args}") + exe_res.append((ExecutorError.SIGNING_ERROR, ve)) return exe_res