diff --git a/.idea/misc.xml b/.idea/misc.xml index 58ebae9..5e70f00 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/tonpy.iml b/.idea/tonpy.iml index e5ec395..fa33167 100644 --- a/.idea/tonpy.iml +++ b/.idea/tonpy.iml @@ -5,8 +5,9 @@ + - + \ No newline at end of file diff --git a/setup.py b/setup.py index 6f682a2..0b96bfb 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def finalize_options(self): setup( name="tonpy" if not IS_DEV else "tonpy-dev", - version="0.0.0.1.2b0" if not IS_DEV else "0.0.0.4.7b1", + version="0.0.0.1.2b0" if not IS_DEV else "0.0.0.4.7c1", author="Disintar LLP", author_email="andrey@head-labs.com", description="Types / API for TON blockchain", diff --git a/src/tonpy/blockscanner/blockscanner.py b/src/tonpy/blockscanner/blockscanner.py index a58ba1f..1021486 100644 --- a/src/tonpy/blockscanner/blockscanner.py +++ b/src/tonpy/blockscanner/blockscanner.py @@ -108,13 +108,19 @@ def process_subscriptions(data, raise NotImplementedError("Emulation not supported yet") -def get_mega_libs(num_try=100): +def get_mega_libs(dton_key, num_try=100): cur = 0 while cur < num_try: try: query = '''query{mega_libs_cell}''' - response = requests.post("https://dton.io/graphql", json={'query': query}) + + if dton_key is not None: + url = f"https://dton.io/{dton_key}/graphql" + else: + url = f"https://dton.io/graphql" + + response = requests.post(url, json={'query': query}) return response.json()['data']['mega_libs_cell'] except Exception as e: logger.error(f"Can't get dton.io/graphql: {e}, {tb.format_exc()}") @@ -498,7 +504,8 @@ def __init__(self, live_load_enable: bool = False, load_chunks: typing.List[typing.Tuple[int, int]] = None, allow_skip_mc_in_live: bool = True, - blocks_to_load: List[BlockIdExt] = None): + blocks_to_load: List[BlockIdExt] = None, + dton_key: str = None): """ :param lcparams: Params for LiteClient @@ -583,7 +590,7 @@ def __init__(self, self.account_subscriptions = account_subscriptions self.emulate_before_output = emulate_before_output self.known_key_blocks = {} - self.mega_libs = get_mega_libs() + self.mega_libs = get_mega_libs(dton_key) self.process_raw = raw_process is not None if self.process_raw: @@ -901,7 +908,7 @@ def load_historical(self): if self.loglevel > 1: logger.debug(f"Start get mega libs: {time() - key_blocks_start_at}") - mega_libs = get_mega_libs() + mega_libs = self.mega_libs for i in shards_data: i['libs'] = mega_libs diff --git a/src/tonpy/tests/test_vmdict_large_keys.py b/src/tonpy/tests/test_vmdict_large_keys.py index 6ad8c68..1bd470e 100644 --- a/src/tonpy/tests/test_vmdict_large_keys.py +++ b/src/tonpy/tests/test_vmdict_large_keys.py @@ -7,6 +7,9 @@ import pytest +from tonpy import LiteClient +from tonpy.autogen.block import Account + path_root = Path(__file__).parents[2] sys.path.append(str(path_root)) @@ -62,7 +65,7 @@ def test_set_get_large(): d.set_ref(CellBuilder().store_uint(1, 8).store_uint(2 ** 254, 256).end_cell().begin_parse(), v2) d.set_builder(CellBuilder().store_uint(2, 8).store_uint(2 ** 254, 256).end_cell().begin_parse(), - CellBuilder().store_uint(0, 8)) + CellBuilder().store_uint(0, 8)) value = d.lookup(CellBuilder().store_uint(0, 8).store_uint(2 ** 254, 256).end_cell().begin_parse()) assert value.get_hash() == v1.get_hash() diff --git a/src/tonpy/tvm/c7.py b/src/tonpy/tvm/c7.py index fd131c1..f89ea4c 100644 --- a/src/tonpy/tvm/c7.py +++ b/src/tonpy/tvm/c7.py @@ -1,8 +1,12 @@ from typing import Union, List -from tonpy.types import Cell, CellSlice, Stack, begin_cell, Address +from tonpy.types.cell import Cell +from tonpy.types.cellslice import CellSlice +from tonpy.types.cellbuilder import begin_cell +from tonpy.types.address import Address from datetime import datetime from tonpy.types.blockid import BlockIdExt +from tonpy.types.stack import Stack class C7: diff --git a/src/tonpy/tvm/emulator.py b/src/tonpy/tvm/emulator.py index 2a23ab5..ff79886 100644 --- a/src/tonpy/tvm/emulator.py +++ b/src/tonpy/tvm/emulator.py @@ -2,7 +2,7 @@ from tonpy.libs.python_ton import PyEmulator -from tonpy import Stack, StackEntry +from tonpy.types.stack import Stack, StackEntry from tonpy.types import VmDict, Cell, CellSlice, begin_cell from tonpy.types.blockid import BlockId from typing import Union, Tuple, List diff --git a/src/tonpy/tvm/tvm.py b/src/tonpy/tvm/tvm.py index 6e574ca..75025fb 100644 --- a/src/tonpy/tvm/tvm.py +++ b/src/tonpy/tvm/tvm.py @@ -58,7 +58,13 @@ def clear_stack(self) -> bool: def fetch_detailed_step_info(self) -> None: self.vm_steps_detailed = [StepInfo(i) for i in self.tvm.get_stacks()] ops = self.tvm.get_ops() - assert len(ops) == len(self.vm_steps_detailed) + if len(ops) != len(self.vm_steps_detailed): + if len(self.vm_steps_detailed) - len(ops) == 1: + self.vm_steps_detailed = self.vm_steps_detailed[1:] + else: + logger.error(f"VM steps: {len(ops)} != {len(self.vm_steps_detailed)}") + return + for i, op in enumerate(ops): self.vm_steps_detailed[i].next_op = op @@ -113,6 +119,50 @@ def exit_code(self) -> int: return ~self.tvm.exit_code return self.tvm.exit_code + def exit_code_description(self): + exit_codes = { + 0: {"Phase": "Compute Phase", "Description": "Standard successful execution exit code."}, + 1: {"Phase": "Compute Phase", "Description": "Alternative successful execution exit code."}, + 2: {"Phase": "Compute Phase", + "Description": "Stack underflow. Last op-code consumed more elements than there are on the stacks."}, + 3: {"Phase": "Compute Phase", + "Description": "Stack overflow. More values have been stored on a stack than allowed by this version of TVM."}, + 4: {"Phase": "Compute Phase", + "Description": "Integer overflow. Integer does not fit into −2^256 ≤ x < 2^256 or a division by zero has occurred."}, + 5: {"Phase": "Compute Phase", "Description": "Integer out of expected range."}, + 6: {"Phase": "Compute Phase", + "Description": "Invalid opcode. Instruction is unknown in the current TVM version."}, + 7: {"Phase": "Compute Phase", + "Description": "Type check error. An argument to a primitive is of an incorrect value type."}, + 8: {"Phase": "Compute Phase", + "Description": "Cell overflow. Writing to builder is not possible since after operation there would be more than 1023 bits or 4 references."}, + 9: {"Phase": "Compute Phase", + "Description": "Cell underflow. Read from slice primitive tried to read more bits or references than there are."}, + 10: {"Phase": "Compute Phase", + "Description": "Dictionary error. Error during manipulation with dictionary (hashmaps)."}, + 11: {"Phase": "Compute Phase", + "Description": "Most often caused by trying to call get-method whose id wasn't found in the code (missing method_id modifier or wrong get-method name specified when trying to call it)."}, + 12: {"Phase": "Compute Phase", "Description": "Thrown by TVM in situations deemed impossible."}, + 13: {"Phase": "Compute Phase", + "Description": "Out of gas error. Thrown by TVM when the remaining gas becomes negative."}, + 32: {"Phase": "Action Phase", + "Description": "Action list is invalid. Set during action phase if c5 register after execution contains unparsable object."}, + 33: {"Phase": "Action Phase", "Description": "Action list is too long."}, + 34: {"Phase": "Action Phase", + "Description": "Action is invalid or not supported. Set during action phase if current action cannot be applied."}, + 35: {"Phase": "Action Phase", "Description": "Invalid Source address in outbound message."}, + 36: {"Phase": "Action Phase", "Description": "Invalid Destination address in outbound message."}, + 37: {"Phase": "Action Phase", + "Description": "Not enough TON. Message sends too much TON (or there is not enough TON after deducting fees)."}, + 38: {"Phase": "Action Phase", "Description": "Not enough extra-currencies."}, + 40: {"Phase": "Action Phase", + "Description": "Not enough funds to process a message. This error is thrown when there is only enough gas to cover part of the message, but does not cover it completely."}, + 43: {"Phase": "Action Phase", + "Description": "The maximum number of cells in the library is exceeded or the maximum depth of the Merkle tree is exceeded."} + } + + return exit_codes[self.exit_code] + @property def vm_steps(self) -> int: return self.tvm.vm_steps diff --git a/src/tonpy/types/__init__.py b/src/tonpy/types/__init__.py index 6c3a3bf..89f98cb 100644 --- a/src/tonpy/types/__init__.py +++ b/src/tonpy/types/__init__.py @@ -2,7 +2,7 @@ from tonpy.types.cell import Cell from tonpy.types.cellslice import CellSlice -from tonpy.types.cellbuilder import CellBuilder +from tonpy.types.cellbuilder import CellBuilder, begin_cell from tonpy.types.vmdict import VmDict, TypedVmDict, TypedDataWithExtra, DataWithExtra, AugmentedData, TypedAugmentedData from tonpy.types.tlb import TLB, RecordBase from tonpy.types.tlb_types import RefT, NatWidth, TLBComplex, Int, UInt, Bits, NatLeq, NatLess @@ -13,7 +13,3 @@ from tonpy.types.liteclient import * from tonpy.types.address import * from tonpy.types.vmdict_extra import * - - -def begin_cell(): - return CellBuilder() diff --git a/src/tonpy/types/address.py b/src/tonpy/types/address.py index ce9025c..6346d39 100644 --- a/src/tonpy/types/address.py +++ b/src/tonpy/types/address.py @@ -67,6 +67,7 @@ def shard_prefix(self, size) -> int: return self.my_address.shard_prefix(size) def to_cs(self) -> CellSlice: + from tonpy.types.cellbuilder import CellBuilder return CellBuilder().store_address(self).end_cell().begin_parse() def __eq__(self, other): diff --git a/src/tonpy/types/cellbuilder.py b/src/tonpy/types/cellbuilder.py index ea7be17..7ce5955 100644 --- a/src/tonpy/types/cellbuilder.py +++ b/src/tonpy/types/cellbuilder.py @@ -418,3 +418,7 @@ def __repr__(self): r = self.refs return f"" + + +def begin_cell(): + return CellBuilder() diff --git a/src/tonpy/types/liteclient.py b/src/tonpy/types/liteclient.py index c573081..cda5398 100644 --- a/src/tonpy/types/liteclient.py +++ b/src/tonpy/types/liteclient.py @@ -8,8 +8,12 @@ from tonpy.libs.python_ton import PyLiteClient, ipv4_int_to_str, globalSetVerbosity, BlockId as ton_BlockId, \ BlockIdExt as ton_BlockIdExt +from tonpy.types.lite_utils.constants import MASTER_SHARD +from tonpy.types.cellbuilder import CellBuilder +from tonpy.types.stack import Stack from tonpy.types.cell import Cell from tonpy.types.address import Address +from tonpy.tvm.tvm import TVM, method_name_to_id from tonpy.types.keys import PublicKey from base64 import b64decode @@ -74,6 +78,23 @@ def __init__(self, ac): self.gen_lt = ac.gen_lt self.gen_utime = ac.gen_utime + def to_shard_state(self): + if not self.root.is_null(): + return CellBuilder() \ + .store_ref(self.root) \ + .store_uint(int(self.last_trans_hash, 16), 256) \ + .store_uint(self.last_trans_lt, 64).end_cell() + else: + return CellBuilder() \ + .store_ref(CellBuilder().store_uint(0, 1).end_cell()) \ + .store_uint(int(self.last_trans_hash, 16), 256) \ + .store_uint(self.last_trans_lt, 64) \ + .end_cell() + + def get_parsed(self): + from tonpy.autogen.block import Account + return Account().cell_unpack(self.root, rec_unpack=True) + class BlockHdrInfo: __slots__ = ['blk_id', 'proof', 'virt_blk_root', 'mode'] @@ -148,13 +169,6 @@ def __init__(self, servers: list, logprefix: str = ''): random.shuffle(servers) self.servers = servers - - # for s in servers: - # if self.check_server(s): - # self.servers.append(s) - # print(len(self.servers), len(servers)) - # print(servers) - self.current = -1 self.client: Optional[LiteClient] = None self.timeout = timeout @@ -549,6 +563,52 @@ def wait_connected(self, timeout: int): """Wait until connected with timeout, throw error if not connected""" return self.client.wait_connected(datetime.datetime.now().timestamp() + timeout) + def run_get_method(self, account: Address, + method: str, + stack: Union[Stack, List] = None, + mc_seqno: int = None, + block: BlockIdExt = None, + c7=None, + allow_non_success=False, + gas_limit=5000000, + gas_max=5000000): + from tonpy.tvm.c7 import C7 + + if stack is None: + stack = [] + + if mc_seqno is not None: + if block: + raise ValueError(f"Can't run get_method with block {block} because block {mc_seqno} is provided") + block = BlockId(workchain=-1, shard=MASTER_SHARD, seqno=mc_seqno) + + if block is None: + block = self.get_masterchain_info_ext().last + + account_state = self.get_account_state(account, block).get_parsed() + + code = account_state.storage.state.x.code.value + data = account_state.storage.state.x.data.value + + tvm = TVM(data=data, code=code, allow_debug=True) + tvm.set_gas_limit(gas_limit, gas_max) + + if c7 is None: + config = self.get_config_all(block) + + # Todo: last_mc_blocks, prev_key_block, income_extra + tvm.set_c7(C7( + time=datetime.datetime.now(), + balance_grams=account_state.storage.balance.grams.amount.value, + address=account, + my_code=code, + global_config=config[1].get_cell() + )) + + tvm.set_stack([*stack, method_name_to_id(method)]) + + return tvm.run(allow_non_success=True) + @staticmethod def get_one(timeout: int = 1, threads: int = 1) -> "LiteClient": server = random.choice(servers) diff --git a/src/tonpy/types/stack.py b/src/tonpy/types/stack.py index d1cd231..ccdb366 100644 --- a/src/tonpy/types/stack.py +++ b/src/tonpy/types/stack.py @@ -2,6 +2,8 @@ from tonpy.libs.python_ton import PyStackEntry, PyStack, make_tuple, deserialize_stack_entry, deserialize_stack, \ PyContinuation + +from tonpy.types.address import Address from tonpy.types import Cell, CellSlice, CellBuilder from typing import Union, Iterable, List from enum import Enum @@ -60,6 +62,8 @@ def __init__(self, value: "None | Cell | CellSlice | int | CellBuilder | list | self.entry = StackEntry.create_tuple(value).entry elif isinstance(value, Continuation): self.entry = PyStackEntry(continuation=value) + elif isinstance(value, Address): + self.entry = PyStackEntry(cell_slice=value.to_cs().cell_slice) else: raise ValueError(f"Type {type(value)} is not supported")