diff --git a/CHANGELOG.md b/CHANGELOG.md index 86da803..e0e820e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- [enhancement](https://github.com/FrankC01/pysui/issues/243) SerialTransactionExecutor implemented + ### Fixed ### Changed diff --git a/doc/source/graphql_serial_exc.rst b/doc/source/graphql_serial_exc.rst index 4907a92..7da02a4 100644 --- a/doc/source/graphql_serial_exc.rst +++ b/doc/source/graphql_serial_exc.rst @@ -53,5 +53,5 @@ In this example the two transaction times are, respectivley: .. code-block:: shell :linenos: - serial_exec DEBUG tx execution 3.573889970779419 - serial_exec DEBUG tx execution 0.1877460479736328 + serial_exec DEBUG tx execution 0.783149003982544 + serial_exec DEBUG tx execution 0.1889028549194336 diff --git a/pysui/sui/sui_pgql/execute/cache.py b/pysui/sui/sui_pgql/execute/cache.py index 58882bd..c6f8cfa 100644 --- a/pysui/sui/sui_pgql/execute/cache.py +++ b/pysui/sui/sui_pgql/execute/cache.py @@ -7,12 +7,15 @@ from abc import ABC, abstractmethod import asyncio +import logging from typing import Any, Union from dataclasses import dataclass import pysui.sui.sui_types.bcs_txne as bcs import pysui.sui.sui_pgql.pgql_types as ptypes +logger = logging.getLogger("serial_exec") + @dataclass class ObjectCacheEntry: @@ -253,4 +256,5 @@ async def applyEffects(self, effects: bcs.TransactionEffects): else: pass if added or deleted: + logger.debug(f"Effects results: Deleted {deleted} Added {added}") await asyncio.gather(self.delete_objects(deleted), self.add_objects(added)) diff --git a/pysui/sui/sui_pgql/execute/caching_exec.py b/pysui/sui/sui_pgql/execute/caching_exec.py index 90a282d..0d8a071 100644 --- a/pysui/sui/sui_pgql/execute/caching_exec.py +++ b/pysui/sui/sui_pgql/execute/caching_exec.py @@ -19,8 +19,6 @@ from .caching_txn import CachingTransaction logger = logging.getLogger("serial_exec") -# logger = logging.getLogger("async_executor") -# logger.setLevel(logging.DEBUG) class AsyncCachingTransactionExecutor: @@ -127,8 +125,9 @@ async def _smash_gas( ) # If one return it if len(coin_list) == 1: - logger.debug("_smash_gas has 1 coin, returning") - return coin_list[0] + ret_coin = coin_list[0] + logger.debug(f"_smash_gas has 1 coin, returning version {ret_coin.version}") + return ret_coin # Otherwise smash and return it tx = AsyncSuiTransaction(client=self._client) use_as_gas = coin_list.pop(0) @@ -142,21 +141,16 @@ async def _smash_gas( if res.is_err(): raise ValueError(f"Failed smashing coins with {res.result_string}") - # Wait for commit - res = await self._client.wait_for_transaction(res.result_data.digest) - if res.is_ok(): - res = await self._client.execute_query_node( - with_node=qn.GetObject(object_id=use_as_gas.coin_object_id) - ) - if res.is_ok(): - logger.debug(f"_smash_gas post merge to {res.result_data.version}") - use_as_gas.object_digest = res.result_data.object_digest - use_as_gas.version = res.result_data.version - else: - raise ValueError("Failed fetching gas coin updates after smashing") - if res.is_err(): - raise ValueError("Failed waiting on transaction") + mresult: ptypes.ExecutionResultGQL = res.result_data + tx_effects = bcst.TransactionEffects.deserialize( + base64.b64decode(mresult.bcs) + ).value + _, effchange = tx_effects.changedObjects[tx_effects.gasObjectIndex.value] + edigest, _ = effchange.outputState.value + use_as_gas.version = tx_effects.lamportVersion + use_as_gas.object_digest = edigest.to_digest_str() + logger.debug(f"Merge gas returning version {use_as_gas.version}") return use_as_gas async def _resolve_object_inputs(self, txn: CachingTransaction): diff --git a/pysui/sui/sui_pgql/execute/caching_tx_builder.py b/pysui/sui/sui_pgql/execute/caching_tx_builder.py index 5d1daec..805f312 100644 --- a/pysui/sui/sui_pgql/execute/caching_tx_builder.py +++ b/pysui/sui/sui_pgql/execute/caching_tx_builder.py @@ -374,7 +374,7 @@ def transfer_objects( ], ) -> bcs.Argument: """Setup a TransferObjects command and return it's result Argument.""" - logger.info("TransferObjects transaction") + logger.debug("TransferObjects transaction") receiver_arg = ( recipient if isinstance(recipient, bcs.Argument) diff --git a/pysui/sui/sui_pgql/execute/serial_exec.py b/pysui/sui/sui_pgql/execute/serial_exec.py index 7d99270..da31ed3 100644 --- a/pysui/sui/sui_pgql/execute/serial_exec.py +++ b/pysui/sui/sui_pgql/execute/serial_exec.py @@ -11,8 +11,7 @@ import time from typing import Any, Coroutine, Optional, Union -logger = logging.getLogger("serial_exec") -logger.setLevel(logging.DEBUG) +ser_txn_exc_logger = logging.getLogger("serial_exec") from pysui import AsyncGqlClient from pysui.sui.sui_pgql.pgql_txb_signing import SigningMultiSig @@ -38,7 +37,7 @@ def _get_gascoin_from_effects(effects: bcst.TransactionEffects) -> bcs.ObjectRef raise ValueError("Unexpected gas object state") edigest, _eowner = effchange.outputState.value - logger.debug("Have gas coin from effects") + ser_txn_exc_logger.debug("Have gas coin from effects") return bcs.ObjectReference(gas_address, v2effects.lamportVersion, edigest) @@ -69,23 +68,25 @@ async def new_transaction( self, ) -> Coroutine[Any, Any, CachingTransaction]: """.""" - logger.debug("Generate new transaction") + ser_txn_exc_logger.debug("Generate new transaction") return CachingTransaction(client=self._client, sender=self._signer) async def _cache_gas_coin( self, effects: bcst.TransactionEffects ) -> Coroutine[Any, Any, None]: """Retrive gas coin information from effects.""" - logger.debug("Gas Coin Caching from effects") + ser_txn_exc_logger.debug("Gas Coin Caching from effects") if effects.enum_name != "V2": - logger.debug("Effects != V2") + ser_txn_exc_logger.debug("Effects != V2") return None gascoin = _get_gascoin_from_effects(effects) if gascoin: - logger.debug(f"Have gas coin with version {gascoin.SequenceNumber}") + ser_txn_exc_logger.debug( + f"Have gas coin with version {gascoin.SequenceNumber}" + ) await self._cache.cache.setCustom("gasCoin", gascoin) else: - logger.debug("DON'T Have gas coin") + ser_txn_exc_logger.debug("DON'T Have gas coin") await self._cache.cache.deleteCustom("gasCoin") async def apply_effects(self, effects_str: str): @@ -107,19 +108,44 @@ async def wait_for_last_transaction(self): async def _build_transaction(self, transaction: CachingTransaction) -> str: """Builds TransactionData for execution.""" - logger.debug("in _build_transaction") + ser_txn_exc_logger.debug("in _build_transaction") gcoin: bcs.ObjectReference = await self._cache.cache.getCustom("gasCoin") if gcoin: - logger.debug( + ser_txn_exc_logger.debug( f"Have gas coin {gcoin.SequenceNumber}, setting gas payment in txn" ) transaction.set_gas_payment(gcoin) else: - logger.debug("No gas coin") + ser_txn_exc_logger.debug("No gas coin") transaction.set_gas_budget_if_notset(self._default_gas_budget) - logger.debug("Calling cache build transaction") + ser_txn_exc_logger.debug("Calling cache build transaction") return await self._cache.build_transaction(transaction) + def _sign_transaction(self, tx_str: str) -> list[str]: + """Sign the transaction. + + :param tx_str: base64 encoded TransactionData str + :type tx_str: str + :raises ValueError: if using multi-sign erroneously + :return: list of base64 encoded signatures + :rtype: list[str] + """ + sig_list: list[str] = [] + if isinstance(self._signer, str): + sig_list.append( + self._client.config.active_group.keypair_for_address( + address=self._signer + ).new_sign_secure(tx_str) + ) + else: + if self._signer._can_sign_msg: + sig_list.append( + self._signer.multi_sig.sign(tx_str, self.signer.pub_keys) + ) + else: + raise ValueError("BaseMultiSig can not sign for execution") + return [x.value for x in sig_list] + async def execute_transactions( self, transactions: list[CachingTransaction], @@ -136,42 +162,25 @@ async def execute_transactions( """ exe_res: list[ptypes.ExecutionResultGQL] = [] for tx in transactions: - if logger.getEffectiveLevel() == logging.DEBUG: - start_time = time.time() - logger.debug("Building transaction") + start_time = time.time() + ser_txn_exc_logger.debug("Building transaction") tx_str = await self._build_transaction(tx) - logger.debug(f"Signing {tx_str}") - # Sign the transaction - sig_list: list[str] = [] - if isinstance(self._signer, str): - sig_list.append( - self._client.config.active_group.keypair_for_address( - address=self._signer - ).new_sign_secure(tx_str) - ) - else: - if self._signer._can_sign_msg: - sig_list.append( - self._signer.multi_sig.sign(tx_str, self.signer.pub_keys) - ) - else: - raise ValueError("BaseMultiSig can not sign for execution") + ser_txn_exc_logger.debug(f"Signing {tx_str}") + try: - # TODO: Clean up using legacy pysui Signature type - logger.debug("Cache transaction execution") + 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, [x.value for x in sig_list], **kwargs - ) + await self._cache.execute_transaction(tx_str, sig_list, **kwargs) ) await self.apply_effects(results.bcs) - if logger.getEffectiveLevel() == logging.DEBUG: - end_time = time.time() - logger.debug(f"tx execution {end_time-start_time}") + end_time = time.time() + ser_txn_exc_logger.info(f"tx execution {end_time-start_time}") exe_res.append(results) except ValueError as exc: - logger.debug("Error callingi cache execute") + ser_txn_exc_logger.debug("Error callingi cache execute") await self.reset_cache() raise exc diff --git a/pysui/sui/sui_pgql/pgql_clients.py b/pysui/sui/sui_pgql/pgql_clients.py index a6261a7..ab53e59 100644 --- a/pysui/sui/sui_pgql/pgql_clients.py +++ b/pysui/sui/sui_pgql/pgql_clients.py @@ -10,15 +10,9 @@ import logging import asyncio from typing import Callable, Any, Optional, Union -from deprecated.sphinx import versionchanged, versionadded, deprecated +from deprecated.sphinx import versionchanged, versionadded from gql import Client, gql from gql.client import ReconnectingAsyncClientSession -from gql.transport.requests import log as requests_logger - -import httpx - -from gql.transport.httpx import HTTPXAsyncTransport - from gql.transport import exceptions as texc from gql.dsl import ( DSLSchema, @@ -28,18 +22,14 @@ from graphql.utilities.print_schema import print_schema from graphql.language.printer import print_ast -# from graphql.language.printer import print_ast - +import httpx -from pysui import SuiConfig, SuiRpcResult, PysuiConfiguration +from pysui import SuiRpcResult, PysuiConfiguration from pysui.sui.sui_pgql.pgql_validators import TypeValidator import pysui.sui.sui_pgql.pgql_types as pgql_type -from pysui.sui.sui_pgql.pgql_configs import pgql_config, SuiConfigGQL +from pysui.sui.sui_pgql.pgql_configs import SuiConfigGQL import pysui.sui.sui_pgql.pgql_schema as scm -import pysui.sui.sui_constants as cnst -# GQL logging -requests_logger.setLevel(logging.NOTSET) # Standard library logging setup logger = logging.getLogger("pgql_client")