diff --git a/requirements.txt b/requirements.txt index c53965e..2d18ead 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ crc16==0.1.1 -tonsdk==1.0.6 +tonsdk==1.0.7 requests==2.27.1 pynacl==1.5.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 7b32fee..00fe26f 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="ton", - version="0.24", + version="0.25", author="psylopunk", author_email="psylopunk@protonmail.com", description="Python client for The Open Network", @@ -16,7 +16,7 @@ install_requires=[ 'crc16>=0.1.1', 'requests>=2.27.1', - 'tonsdk>=1.0.6', + 'tonsdk>=1.0.7', 'PyNaCl>=1.5.0' ], package_data={ diff --git a/ton/account/ft_methods.py b/ton/account/ft_methods.py index 28f804a..950a9b5 100644 --- a/ton/account/ft_methods.py +++ b/ton/account/ft_methods.py @@ -33,8 +33,8 @@ async def get_wallet_address(self, owner_address, **kwargs): 'address': read_address(Cell.one_from_boc(b64decode(response.stack[0].cell.bytes))) } - async def get_wallet_data(self): - response = await self.run_get_method('get_wallet_data') + async def get_wallet_data(self, **kwargs): + response = await self.run_get_method('get_wallet_data', **kwargs) if response.exit_code != 0: raise Exception('get_wallet_data exit_code: {}'.format(response.exit_code)) diff --git a/ton/account/smc_methods.py b/ton/account/smc_methods.py index a32555e..2258843 100644 --- a/ton/account/smc_methods.py +++ b/ton/account/smc_methods.py @@ -4,20 +4,20 @@ class SmcMethods: - async def load_smc(self): + async def load_smc(self, **kwargs): self.smc_id = (await self.client.tonlib_wrapper.execute( - Smc_Load(self.account_address) + Smc_Load(self.account_address), **kwargs )).id - async def run_get_method(self, method: Union[str, int], stack: list = [], force=False): + async def run_get_method(self, method: Union[str, int], stack: list = [], force=False, **kwargs): if self.smc_id is None or force is True: - await self.load_smc() + await self.load_smc(**kwargs) return await self.client.tonlib_wrapper.execute( - Smc_RunGetMethod(self.smc_id, method, stack) + Smc_RunGetMethod(self.smc_id, method, stack), **kwargs ) - async def send_message(self, body: bytes): + async def send_message(self, body: bytes, **kwargs): return await self.client.tonlib_wrapper.execute( - Raw_CreateAndSendMessage(self.account_address, body) - ) \ No newline at end of file + Raw_CreateAndSendMessage(self.account_address, body), **kwargs + ) diff --git a/ton/account/state_methods.py b/ton/account/state_methods.py index 572a17d..18d1647 100644 --- a/ton/account/state_methods.py +++ b/ton/account/state_methods.py @@ -2,29 +2,30 @@ from ..tl.types import Internal_TransactionId from ..utils import sha256, contracts + class StateMethods: - async def load_state(self): + async def load_state(self, **kwargs): self.state = await self.client.tonlib_wrapper.execute( - Raw_GetAccountState(self.account_address) + Raw_GetAccountState(self.account_address), **kwargs ) - async def get_state(self, force=False): + async def get_state(self, force=False, **kwargs): if self.state is None or force: - await self.load_state() + await self.load_state(**kwargs) return self.state - async def detect_type(self): - state = await self.get_state() + async def detect_type(self, **kwargs): + state = await self.get_state(**kwargs) return contracts.get(sha256(state.code), None) - async def get_balance(self): + async def get_balance(self, **kwargs): return int( - (await self.get_state(force=True)).balance # in nanocoins + (await self.get_state(force=True, **kwargs)).balance # nanoTONs ) async def get_transactions(self, from_transaction_lt=None, from_transaction_hash=None, to_transaction_lt=0, limit=10): - if from_transaction_lt == None or from_transaction_hash == None: + if from_transaction_lt is None or from_transaction_hash is None: state = await self.get_state(force=True) from_transaction_lt, from_transaction_hash = state.last_transaction_id.lt, state.last_transaction_id.hash diff --git a/ton/account/wallet_methods.py b/ton/account/wallet_methods.py index af503af..e151202 100644 --- a/ton/account/wallet_methods.py +++ b/ton/account/wallet_methods.py @@ -97,22 +97,22 @@ async def transfer_nft( return await self.transfer(nft_address, self.client.to_nano(0.05), data=body) - async def seqno(self): - result = await self.run_get_method('seqno', force=True) + async def seqno(self, **kwargs): + result = await self.run_get_method('seqno', force=True, **kwargs) if result.exit_code != 0: return 0 return int(result.stack[0].number.number) - async def get_public_key(self): + async def get_public_key(self, **kwargs): if hasattr(self, 'key'): return b64decode( await self.client.export_key(InputKeyRegular(self.key, local_password=self.__dict__.get('local_password'))) ) else: try: - result = await self.run_get_method('get_public_key') + result = await self.run_get_method('get_public_key', **kwargs) assert result.exit_code == 0, 'get_public_key failed' return int(result.stack[0].number.number).to_bytes(32, 'big') except Exception as e: diff --git a/ton/client/__init__.py b/ton/client/__init__.py index 11019fb..15af4c0 100644 --- a/ton/client/__init__.py +++ b/ton/client/__init__.py @@ -21,10 +21,10 @@ def __init__( config='https://ton.org/global-config.json', keystore=None, workchain_id=0, - verbosity_level=0 + verbosity_level=0, + default_timeout=10 ): - if keystore is None: # while memory keystore libtonlibjson bug keep - if '.keystore' not in os.listdir(path='.'): os.system('mkdir .keystore') + if keystore is None: # while memory keystore libtonlibjson bug keep keystore = '.keystore' self.loop = None @@ -33,23 +33,22 @@ def __init__( self.keystore = keystore self.workchain_id = workchain_id self.verbosity_level = verbosity_level + self.default_timeout = default_timeout - self.queue = [] + self.queue = [] # legacy self.lock = asyncio.Lock() self.locked = False - async def find_account(self, account_address: Union[AccountAddress, str], preload_state: bool=True): + async def find_account(self, account_address: Union[AccountAddress, str], preload_state: bool=True, **kwargs): """ - Getting a Account object by account address + Getting an Account object by account address :param account_address: :return: Account """ account = Account(account_address, client=self) - if preload_state: await account.load_state() - return account + if preload_state: + await account.load_state(**kwargs) - # TODO: remove in next version - async def wallet_from_exported_key(self, exported_key: str, revision: int=0, workchain_id: int=None): - raise Exception('TonlibClient.wallet_from_exported_key is deprecated, try Tonlibclient.find_wallet instead') \ No newline at end of file + return account diff --git a/ton/client/converter_methods.py b/ton/client/converter_methods.py index 6e7fd70..59a7910 100644 --- a/ton/client/converter_methods.py +++ b/ton/client/converter_methods.py @@ -1,7 +1,7 @@ class ConverterMethods: - def from_nano(self, value): - return round(value / 10 ** 9, 9) + def from_nano(self, value) -> float: + return round(int(value) / 10 ** 9, 9) - def to_nano(self, value): - return int(value * (10 ** 9)) \ No newline at end of file + def to_nano(self, value) -> int: + return int(float(value) * (10 ** 9)) \ No newline at end of file diff --git a/ton/client/function_methods.py b/ton/client/function_methods.py index c6b31e1..6085a50 100644 --- a/ton/client/function_methods.py +++ b/ton/client/function_methods.py @@ -9,7 +9,7 @@ async def set_verbosity_level(self, level): ) - async def send_boc(self, message: bytes): + async def send_boc(self, message: bytes, **kwargs): """ Sending a message to the network @@ -18,7 +18,7 @@ async def send_boc(self, message: bytes): """ query = Raw_SendMessage(message) - return await self.tonlib_wrapper.execute(query) + return await self.tonlib_wrapper.execute(query, **kwargs) async def create_new_key(self, mnemonic_password='', random_extra_seed=None, local_password=None): diff --git a/ton/client/tonlib_methods.py b/ton/client/tonlib_methods.py index 9628237..23449b1 100644 --- a/ton/client/tonlib_methods.py +++ b/ton/client/tonlib_methods.py @@ -10,6 +10,7 @@ logger = getLogger('ton') + class TonlibMethods: @classmethod def enable_unaudited_binaries(cls): @@ -24,7 +25,7 @@ def local_config(self): local['liteservers'] = [local['liteservers'][self.ls_index]] return local - async def execute(self, query, timeout=30): + async def execute(self, query, timeout=None): """ Direct request to liteserver @@ -32,7 +33,7 @@ async def execute(self, query, timeout=30): :param timeout: :return: TLObject """ - result = await self.tonlib_wrapper.execute(query, timeout=timeout) + result = await self.tonlib_wrapper.execute(query, timeout=(timeout or self.default_timeout)) if result.type == 'error': raise Exception(result.message) @@ -63,7 +64,7 @@ async def init_tonlib(self, cdll_path=None): cdll_path = get_tonlib_path() - wrapper = TonLib(self.loop, self.ls_index, cdll_path, self.verbosity_level) + wrapper = TonLib(self.loop, self.ls_index, cdll_path, self.verbosity_level, default_timeout=self.default_timeout) keystore_obj = { '@type': 'keyStoreTypeDirectory', 'directory': self.keystore @@ -109,4 +110,4 @@ async def reconnect(self): logger.info(f'Client #{self.ls_index:03d} reconnecting') self.tonlib_wrapper.shutdown_state = "started" await self.init_tonlib() - logger.info(f'Client #{self.ls_index:03d} reconnected') \ No newline at end of file + logger.info(f'Client #{self.ls_index:03d} reconnected') diff --git a/ton/tonlibjson.py b/ton/tonlibjson.py index 7dd8cf8..17e2344 100644 --- a/ton/tonlibjson.py +++ b/ton/tonlibjson.py @@ -18,10 +18,12 @@ class TonlibException(Exception): pass + class TonlibNoResponse(TonlibException): def __str__(self): return 'tonlibjson did not respond' + class TonlibError(TonlibException): def __init__(self, result): self.result = result @@ -33,18 +35,23 @@ def code(self): def __str__(self): return self.result.get('message') + class LiteServerTimeout(TonlibError): pass + class BlockNotFound(TonlibError): pass + class BlockDeleted(TonlibError): pass + class ExternalMessageNotAccepted(TonlibError): pass + def parse_tonlib_error(result): if result.get('@type') == 'error': message = result.get('message') @@ -59,6 +66,7 @@ def parse_tonlib_error(result): return TonlibError(result) return None + def get_tonlib_path(): arch_name = platform.system().lower() machine = platform.machine().lower() @@ -76,15 +84,13 @@ def get_tonlib_path(): class TonLib: - def __init__(self, loop, ls_index, cdll_path=None, verbosity_level=0): + def __init__(self, loop, ls_index, cdll_path=None, verbosity_level=0, default_timeout=None): self.loop = loop + self.default_timeout = default_timeout + cdll_path = get_tonlib_path() if not cdll_path else cdll_path tonlib = CDLL(cdll_path) - # tonlib_client_set_verbosity_level = tonlib.tonlib_client_set_verbosity_level - # tonlib_client_set_verbosity_level.restype = None - # tonlib_client_set_verbosity_level.argtypes = [c_int] - tonlib_json_client_create = tonlib.tonlib_client_json_create tonlib_json_client_create.restype = c_void_p tonlib_json_client_create.argtypes = [] @@ -140,7 +146,7 @@ def send(self, query): logger.error(f"Exception in tonlibjson.send: {traceback.format_exc()}") raise RuntimeError(f'Error in tonlibjson.send: {ee}') - def receive(self, timeout=10): + def receive(self, timeout=30): result = None try: result = self._tonlib_json_client_receive(self._client, timeout) # time.sleep # asyncio.sleep @@ -151,7 +157,7 @@ def receive(self, timeout=10): result = json.loads(result.decode('utf-8')) return result - def _execute(self, query, timeout=10): + def _execute(self, query, timeout=30): if not self._is_working: raise RuntimeError(f"TonLib failed with state: {self._state}") @@ -163,7 +169,7 @@ def _execute(self, query, timeout=10): self.loop.run_in_executor(None, lambda: self.send(query)) return future_result - async def execute(self, query, timeout=10): + async def execute(self, query, timeout=30): logger.debug(f'SENT' + '\n' + f'{query}') if isinstance(query, TLObject): query = query.to_json() result = await self._execute(query, timeout=timeout) @@ -206,15 +212,14 @@ def cancel_futures(self, cancel_all=False): # tasks async def read_results(self): - timeout = 1 - delta = 5 + timeout = self.default_timeout or 1 receive_func = functools.partial(self.receive, timeout) try: while self._is_working and not self._is_closing: # return reading result result = None try: - result = await asyncio.wait_for(self.loop.run_in_executor(None, receive_func), timeout=timeout + delta) + result = await asyncio.wait_for(self.loop.run_in_executor(None, receive_func), timeout=timeout) except asyncio.TimeoutError: logger.critical(f"Tonlib #{self.ls_index:03d} stuck (timeout error)") self._state = "stuck" diff --git a/ton/utils/cell.py b/ton/utils/cell.py index 1e66156..c29a2fa 100644 --- a/ton/utils/cell.py +++ b/ton/utils/cell.py @@ -4,4 +4,6 @@ def read_address(cell): data = ''.join([str(cell.bits.get(x)) for x in range(cell.bits.length)]) if len(data) < 267: return None - return Address(f"{int(data[3:11], 2)}:{int(data[11:11+256], 2).to_bytes(32, 'big').hex()}") + wc = int(data[3:11], 2) + hashpart = int(data[11:11+256], 2).to_bytes(32, 'big').hex() + return Address(f"{wc if wc != 255 else -1}:{hashpart}")