tonutils 2.0.1b6__py3-none-any.whl → 2.0.1b7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. tonutils/__meta__.py +1 -1
  2. tonutils/clients/adnl/balancer.py +132 -355
  3. tonutils/clients/adnl/client.py +32 -202
  4. tonutils/clients/adnl/mixin.py +268 -0
  5. tonutils/clients/adnl/provider/provider.py +61 -16
  6. tonutils/clients/adnl/provider/transport.py +13 -4
  7. tonutils/clients/adnl/provider/workers/pinger.py +1 -1
  8. tonutils/clients/adnl/utils.py +5 -5
  9. tonutils/clients/base.py +52 -92
  10. tonutils/clients/http/balancer.py +93 -90
  11. tonutils/clients/http/clients/tatum.py +1 -0
  12. tonutils/clients/http/clients/tonapi.py +12 -24
  13. tonutils/clients/http/clients/toncenter.py +15 -33
  14. tonutils/clients/http/provider/base.py +75 -60
  15. tonutils/clients/http/provider/models.py +1 -1
  16. tonutils/clients/http/provider/tonapi.py +0 -5
  17. tonutils/clients/http/provider/toncenter.py +4 -8
  18. tonutils/clients/protocol.py +6 -6
  19. tonutils/contracts/base.py +32 -32
  20. tonutils/contracts/protocol.py +9 -9
  21. tonutils/contracts/wallet/base.py +5 -5
  22. tonutils/contracts/wallet/versions/v5.py +2 -2
  23. tonutils/exceptions.py +29 -13
  24. tonutils/tools/block_scanner/__init__.py +5 -1
  25. tonutils/tools/block_scanner/scanner.py +1 -1
  26. tonutils/tools/status_monitor/monitor.py +6 -6
  27. tonutils/types.py +2 -2
  28. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b7.dist-info}/METADATA +3 -18
  29. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b7.dist-info}/RECORD +33 -32
  30. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b7.dist-info}/WHEEL +0 -0
  31. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b7.dist-info}/entry_points.txt +0 -0
  32. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b7.dist-info}/licenses/LICENSE +0 -0
  33. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b7.dist-info}/top_level.txt +0 -0
@@ -2,32 +2,29 @@ from __future__ import annotations
2
2
 
3
3
  import typing as t
4
4
 
5
- from pytoniq_core import Address, BlockIdExt, Block, Transaction
6
-
5
+ from tonutils.clients.adnl.mixin import LiteMixin
7
6
  from tonutils.clients.adnl.provider import AdnlProvider
8
7
  from tonutils.clients.adnl.provider.config import (
9
8
  get_mainnet_global_config,
10
9
  get_testnet_global_config,
10
+ load_global_config,
11
11
  )
12
12
  from tonutils.clients.adnl.provider.models import (
13
13
  LiteServer,
14
14
  GlobalConfig,
15
- MasterchainInfo,
16
15
  )
17
- from tonutils.clients.adnl.utils import decode_stack, encode_stack
18
16
  from tonutils.clients.base import BaseClient
19
17
  from tonutils.clients.limiter import RateLimiter
18
+ from tonutils.exceptions import NotConnectedError, ClientError
20
19
  from tonutils.types import (
21
20
  BinaryLike,
22
21
  ClientType,
23
- ContractStateInfo,
24
22
  NetworkGlobalID,
25
23
  RetryPolicy,
26
- WorkchainID,
27
24
  )
28
25
 
29
26
 
30
- class LiteClient(BaseClient):
27
+ class LiteClient(LiteMixin, BaseClient):
31
28
  """TON blockchain client for lite-server communication over ADNL provider."""
32
29
 
33
30
  TYPE = ClientType.ADNL
@@ -96,32 +93,20 @@ class LiteClient(BaseClient):
96
93
  return self._provider
97
94
 
98
95
  @property
99
- def is_connected(self) -> bool:
96
+ def connected(self) -> bool:
100
97
  """
101
98
  Check whether the lite-server connection is established.
102
99
 
103
100
  :return: True if connected, False otherwise
104
101
  """
105
- return self._provider.is_connected
106
-
107
- async def __aenter__(self) -> LiteClient:
108
- await self.connect()
109
- return self
110
-
111
- async def __aexit__(
112
- self,
113
- exc_type: t.Optional[t.Type[BaseException]],
114
- exc_value: t.Optional[BaseException],
115
- traceback: t.Optional[t.Any],
116
- ) -> None:
117
- await self.close()
102
+ return self._provider.connected
118
103
 
119
104
  @classmethod
120
105
  def from_config(
121
106
  cls,
122
107
  network: NetworkGlobalID = NetworkGlobalID.MAINNET,
123
108
  *,
124
- config: t.Union[GlobalConfig, t.Dict[str, t.Any]],
109
+ config: t.Union[GlobalConfig, t.Dict[str, t.Any], str],
125
110
  index: int,
126
111
  connect_timeout: float = 2.0,
127
112
  request_timeout: float = 10.0,
@@ -141,7 +126,7 @@ class LiteClient(BaseClient):
141
126
  Public free configs may also be used, but may be unstable under load.
142
127
 
143
128
  :param network: Target TON network
144
- :param config: GlobalConfig instance or raw dict
129
+ :param config: GlobalConfig instance, config file path as string, or raw dict
145
130
  :param index: Index of lite-server entry in the configuration
146
131
  :param connect_timeout: Timeout in seconds for connect/handshake performed
147
132
  by this client.
@@ -152,9 +137,20 @@ class LiteClient(BaseClient):
152
137
  :param retry_policy: Optional retry policy that defines per-error-code retry rules
153
138
  :return: Configured LiteClient instance
154
139
  """
140
+ if isinstance(config, str):
141
+ config = load_global_config(config)
155
142
  if isinstance(config, dict):
156
143
  config = GlobalConfig(**config)
144
+
145
+ liteservers = config.liteservers
146
+ if not 0 <= index < len(liteservers):
147
+ raise ClientError(
148
+ f"{cls.__name__}.from_config: "
149
+ f"liteserver index {index} is out of range "
150
+ f"(available: 0..{len(liteservers) - 1})."
151
+ )
157
152
  ls = config.liteservers[index]
153
+
158
154
  return cls(
159
155
  network=network,
160
156
  ip=ls.host,
@@ -216,194 +212,28 @@ class LiteClient(BaseClient):
216
212
  retry_policy=retry_policy,
217
213
  )
218
214
 
219
- async def _send_boc(self, boc: str) -> None:
220
- return await self.provider.send_message(bytes.fromhex(boc))
221
-
222
- async def _get_blockchain_config(self) -> t.Dict[int, t.Any]:
223
- return await self.provider.get_config_all()
224
-
225
- async def _get_contract_info(self, address: str) -> ContractStateInfo:
226
- return await self.provider.get_account_state(Address(address))
227
-
228
- async def _get_transactions(
229
- self,
230
- address: str,
231
- limit: int = 100,
232
- from_lt: t.Optional[int] = None,
233
- to_lt: t.Optional[int] = None,
234
- ) -> t.List[Transaction]:
235
- to_lt = 0 if to_lt is None else to_lt
236
- state = await self._get_contract_info(address)
237
- account = Address(address).to_tl_account_id()
238
-
239
- if state.last_transaction_lt is None or state.last_transaction_hash is None:
240
- return []
241
-
242
- curr_lt = state.last_transaction_lt
243
- curr_hash = state.last_transaction_hash
244
- transactions: t.List[Transaction] = []
245
-
246
- while curr_lt != 0:
247
- fetch_lt = curr_lt
248
- fetch_hash = curr_hash
249
-
250
- txs = await self.provider.get_transactions(
251
- account=account,
252
- count=16,
253
- from_lt=fetch_lt,
254
- from_hash=fetch_hash,
255
- )
256
- if not txs:
257
- break
258
-
259
- for tx in txs:
260
- if from_lt is not None and tx.lt > from_lt:
261
- continue
262
- if to_lt > 0 and tx.lt <= to_lt:
263
- return transactions[:limit]
264
-
265
- transactions.append(tx)
266
- if len(transactions) >= limit:
267
- return transactions
268
-
269
- last_tx = txs[-1]
270
- curr_lt = last_tx.prev_trans_lt
271
- curr_hash = last_tx.prev_trans_hash.hex()
272
-
273
- return transactions[:limit]
274
-
275
- async def _run_get_method(
276
- self,
277
- address: str,
278
- method_name: str,
279
- stack: t.Optional[t.List[t.Any]] = None,
280
- ) -> t.List[t.Any]:
281
- result = await self.provider.run_smc_method(
282
- address=Address(address),
283
- method_name=method_name,
284
- stack=encode_stack(stack or []),
285
- )
286
- return decode_stack(result or [])
287
-
288
215
  async def connect(self) -> None:
289
216
  """Establish connection to the lite-server."""
290
217
  await self.provider.connect()
291
218
 
292
- async def reconnect(self) -> None:
293
- """Force reconnection to the lite-server."""
294
- await self.provider.reconnect()
295
-
296
219
  async def close(self) -> None:
297
220
  """Close the lite-server connection."""
298
221
  await self.provider.close()
299
222
 
300
- async def get_time(self) -> int:
301
- """
302
- Fetch current network time from lite-server.
303
-
304
- :return: Current UNIX timestamp
223
+ async def _adnl_call(self, method: str, /, *args: t.Any, **kwargs: t.Any) -> t.Any:
305
224
  """
306
- return await self.provider.get_time()
225
+ Execute lite-server call using the current provider.
307
226
 
308
- async def get_version(self) -> int:
227
+ :param method: Provider coroutine method name.
228
+ :param args: Positional arguments forwarded to the provider method.
229
+ :param kwargs: Keyword arguments forwarded to the provider method.
230
+ :return: Provider method result.
309
231
  """
310
- Fetch lite-server protocol version.
311
-
312
- :return: Version number
313
- """
314
- return await self.provider.get_version()
315
-
316
- async def wait_masterchain_seqno(
317
- self,
318
- seqno: int,
319
- timeout_ms: int,
320
- schema_name: str,
321
- data: t.Optional[dict] = None,
322
- ) -> dict:
323
- """
324
- Combine waitMasterchainSeqno with another lite-server query.
325
-
326
- :param seqno: Masterchain seqno to wait for
327
- :param timeout_ms: Wait timeout in milliseconds
328
- :param schema_name: Lite-server TL method name without prefix
329
- :param data: Additional method arguments
330
- :return: Lite-server response as dictionary
331
- """
332
- return await self.provider.wait_masterchain_seqno(
333
- seqno=seqno,
334
- timeout_ms=timeout_ms,
335
- schema_name=schema_name,
336
- data=data,
337
- )
338
-
339
- async def get_masterchain_info(self) -> MasterchainInfo:
340
- """
341
- Fetch basic masterchain information.
342
-
343
- :return: MasterchainInfo instance
344
- """
345
- return await self.provider.get_masterchain_info()
346
-
347
- async def lookup_block(
348
- self,
349
- workchain: WorkchainID,
350
- shard: int,
351
- seqno: t.Optional[int] = None,
352
- lt: t.Optional[int] = None,
353
- utime: t.Optional[int] = None,
354
- ) -> t.Tuple[BlockIdExt, Block]:
355
- """
356
- Locate a block by workchain/shard and one of seqno/lt/utime.
357
-
358
- :param workchain: Workchain identifier
359
- :param shard: Shard identifier
360
- :param seqno: Block sequence number
361
- :param lt: Logical time filter
362
- :param utime: UNIX time filter
363
- :return: Tuple of BlockIdExt and deserialized Block
364
- """
365
- return await self.provider.lookup_block(
366
- workchain=workchain,
367
- shard=shard,
368
- seqno=seqno,
369
- lt=lt,
370
- utime=utime,
371
- )
372
-
373
- async def get_block_header(
374
- self,
375
- block: BlockIdExt,
376
- ) -> t.Tuple[BlockIdExt, Block]:
377
- """
378
- Fetch and deserialize block header by BlockIdExt.
379
-
380
- :param block: BlockIdExt to query
381
- :return: Tuple of BlockIdExt and deserialized Block
382
- """
383
- return await self.provider.get_block_header(block)
384
-
385
- async def get_block_transactions_ext(
386
- self,
387
- block: BlockIdExt,
388
- count: int = 1024,
389
- ) -> t.List[Transaction]:
390
- """
391
- Fetch extended block transactions list.
392
-
393
- :param block: Target block identifier
394
- :param count: Maximum number of transactions per request
395
- :return: List of deserialized Transaction objects
396
- """
397
- return await self.provider.get_block_transactions_ext(block, count=count)
398
-
399
- async def get_all_shards_info(
400
- self,
401
- block: t.Optional[BlockIdExt] = None,
402
- ) -> t.List[BlockIdExt]:
403
- """
404
- Fetch shard info for all workchains at a given masterchain block.
232
+ if not self.connected:
233
+ raise NotConnectedError(
234
+ component=self.__class__.__name__,
235
+ operation=method,
236
+ )
405
237
 
406
- :param block: Masterchain block ID or None to use latest
407
- :return: List of shard BlockIdExt objects
408
- """
409
- return await self.provider.get_all_shards_info(block)
238
+ fn = getattr(self.provider, method)
239
+ return await fn(*args, **kwargs)
@@ -0,0 +1,268 @@
1
+ import typing as t
2
+
3
+ from pytoniq_core import Address, Block, BlockIdExt, Transaction, Account, ShardAccount
4
+
5
+ from tonutils.clients.adnl.provider.models import MasterchainInfo
6
+ from tonutils.clients.adnl.utils import decode_stack, encode_stack
7
+ from tonutils.types import ContractInfo, WorkchainID, AddressLike
8
+
9
+
10
+ class LiteMixin:
11
+
12
+ async def _adnl_call(
13
+ self,
14
+ method: str,
15
+ /,
16
+ *args: t.Any,
17
+ **kwargs: t.Any,
18
+ ) -> t.Any:
19
+ """
20
+ Execute a single lite-server provider call.
21
+
22
+ This is an internal hook used by the mixin to unify implementations between
23
+ LiteClient (direct call) and LiteBalancer (failover / retry call).
24
+
25
+ :param method: Provider coroutine method name (e.g. "get_time", "lookup_block").
26
+ :param args: Positional arguments forwarded to the provider method.
27
+ :param kwargs: Keyword arguments forwarded to the provider method.
28
+ :return: Provider method result.
29
+ """
30
+ raise NotImplementedError("LiteMixin requires `_adnl_call()` implementation.")
31
+
32
+ async def _send_message(self, boc: str) -> None:
33
+ method = "send_message"
34
+ await self._adnl_call(method, bytes.fromhex(boc))
35
+
36
+ async def _get_config(self) -> t.Dict[int, t.Any]:
37
+ method = "get_config"
38
+ return t.cast(t.Dict[int, t.Any], await self._adnl_call(method))
39
+
40
+ async def _get_info(self, address: str) -> ContractInfo:
41
+ method = "get_info"
42
+ return t.cast(
43
+ ContractInfo,
44
+ await self._adnl_call(method, Address(address)),
45
+ )
46
+
47
+ async def _run_get_method(
48
+ self,
49
+ address: str,
50
+ method_name: str,
51
+ stack: t.Optional[t.List[t.Any]] = None,
52
+ ) -> t.List[t.Any]:
53
+ method = "run_get_method"
54
+ res = await self._adnl_call(
55
+ method,
56
+ address=Address(address),
57
+ method_name=method_name,
58
+ stack=encode_stack(stack or []),
59
+ )
60
+ return decode_stack(res or [])
61
+
62
+ async def _get_transactions(
63
+ self,
64
+ address: str,
65
+ limit: int = 100,
66
+ from_lt: t.Optional[int] = None,
67
+ to_lt: t.Optional[int] = None,
68
+ ) -> t.List[Transaction]:
69
+ method = "get_transactions"
70
+
71
+ to_lt_i = 0 if to_lt is None else to_lt
72
+ state = await self._get_info(address)
73
+ account = Address(address).to_tl_account_id()
74
+
75
+ if state.last_transaction_lt is None or state.last_transaction_hash is None:
76
+ return []
77
+
78
+ curr_lt = state.last_transaction_lt
79
+ curr_hash = state.last_transaction_hash
80
+ out: t.List[Transaction] = []
81
+
82
+ while curr_lt != 0:
83
+ fetch_lt = curr_lt
84
+ fetch_hash = curr_hash
85
+
86
+ txs = t.cast(
87
+ t.List[Transaction],
88
+ await self._adnl_call(
89
+ method,
90
+ account=account,
91
+ count=16,
92
+ from_lt=fetch_lt,
93
+ from_hash=fetch_hash,
94
+ ),
95
+ )
96
+ if not txs:
97
+ break
98
+
99
+ for tx in txs:
100
+ if from_lt is not None and tx.lt > from_lt:
101
+ continue
102
+ if to_lt_i > 0 and tx.lt <= to_lt_i:
103
+ return out[:limit]
104
+
105
+ out.append(tx)
106
+ if len(out) >= limit:
107
+ return out
108
+
109
+ last_tx = txs[-1]
110
+ curr_lt = last_tx.prev_trans_lt
111
+ curr_hash = last_tx.prev_trans_hash.hex()
112
+
113
+ return out[:limit]
114
+
115
+ async def get_time(self) -> int:
116
+ """
117
+ Fetch current network time from lite-server.
118
+
119
+ :return: Current UNIX timestamp
120
+ """
121
+ method = "get_time"
122
+ return t.cast(int, await self._adnl_call(method))
123
+
124
+ async def get_version(self) -> int:
125
+ """
126
+ Fetch lite-server protocol version.
127
+
128
+ :return: Version number
129
+ """
130
+ method = "get_version"
131
+ return t.cast(int, await self._adnl_call(method))
132
+
133
+ async def get_masterchain_info(self) -> MasterchainInfo:
134
+ """
135
+ Fetch basic masterchain information.
136
+
137
+ :return: MasterchainInfo instance
138
+ """
139
+ method = "get_masterchain_info"
140
+ return t.cast(MasterchainInfo, await self._adnl_call(method))
141
+
142
+ async def wait_masterchain_seqno(
143
+ self,
144
+ seqno: int,
145
+ timeout_ms: int,
146
+ schema_name: str,
147
+ data: t.Optional[dict] = None,
148
+ ) -> t.Dict[str, t.Any]:
149
+ """
150
+ Combine waitMasterchainSeqno with another lite-server query.
151
+
152
+ :param seqno: Masterchain seqno to wait for
153
+ :param timeout_ms: Wait timeout in milliseconds
154
+ :param schema_name: Lite-server TL method name without prefix
155
+ :param data: Additional method arguments
156
+ :return: Lite-server response as dictionary
157
+ """
158
+ method = "wait_masterchain_seqno"
159
+ return t.cast(
160
+ t.Dict[str, t.Any],
161
+ await self._adnl_call(
162
+ method,
163
+ seqno=seqno,
164
+ timeout_ms=timeout_ms,
165
+ schema_name=schema_name,
166
+ data=data,
167
+ ),
168
+ )
169
+
170
+ async def lookup_block(
171
+ self,
172
+ workchain: WorkchainID,
173
+ shard: int,
174
+ seqno: t.Optional[int] = None,
175
+ lt: t.Optional[int] = None,
176
+ utime: t.Optional[int] = None,
177
+ ) -> t.Tuple[BlockIdExt, Block]:
178
+ """
179
+ Locate a block by workchain/shard and one of seqno/lt/utime.
180
+
181
+ :param workchain: Workchain identifier
182
+ :param shard: Shard identifier
183
+ :param seqno: Block sequence number
184
+ :param lt: Logical time filter
185
+ :param utime: UNIX time filter
186
+ :return: Tuple of BlockIdExt and deserialized Block
187
+ """
188
+ method = "lookup_block"
189
+ return t.cast(
190
+ t.Tuple[BlockIdExt, Block],
191
+ await self._adnl_call(
192
+ method,
193
+ workchain=workchain,
194
+ shard=shard,
195
+ seqno=seqno,
196
+ lt=lt,
197
+ utime=utime,
198
+ ),
199
+ )
200
+
201
+ async def get_block_header(
202
+ self,
203
+ block: BlockIdExt,
204
+ ) -> t.Tuple[BlockIdExt, Block]:
205
+ """
206
+ Fetch and deserialize block header by BlockIdExt.
207
+
208
+ :param block: BlockIdExt to query
209
+ :return: Tuple of BlockIdExt and deserialized Block
210
+ """
211
+ method = "get_block_header"
212
+ return t.cast(
213
+ t.Tuple[BlockIdExt, Block],
214
+ await self._adnl_call(method, block),
215
+ )
216
+
217
+ async def get_block_transactions(
218
+ self,
219
+ block: BlockIdExt,
220
+ count: int = 1024,
221
+ ) -> t.List[Transaction]:
222
+ """
223
+ Fetch extended block transactions list.
224
+
225
+ :param block: Target block identifier
226
+ :param count: Maximum number of transactions per request
227
+ :return: List of deserialized Transaction objects
228
+ """
229
+ method = "get_block_transactions"
230
+ return t.cast(
231
+ t.List[Transaction],
232
+ await self._adnl_call(method, block, count=count),
233
+ )
234
+
235
+ async def get_all_shards_info(
236
+ self,
237
+ block: t.Optional[BlockIdExt] = None,
238
+ ) -> t.List[BlockIdExt]:
239
+ """
240
+ Fetch shard info for all workchains at a given masterchain block.
241
+
242
+ :param block: Masterchain block ID or None to use latest
243
+ :return: List of shard BlockIdExt objects
244
+ """
245
+ method = "get_all_shards_info"
246
+ return t.cast(
247
+ t.List[BlockIdExt],
248
+ await self._adnl_call(method, block),
249
+ )
250
+
251
+ async def get_account_state(
252
+ self,
253
+ address: AddressLike,
254
+ ) -> t.Tuple[t.Optional[Account], t.Optional[ShardAccount]]:
255
+ """
256
+ Fetch account state and shard account from lite-server.
257
+
258
+ :param address: Contract address as Address object or string
259
+ :return: Tuple of (Account | None, ShardAccount | None)
260
+ """
261
+ if isinstance(address, str):
262
+ address = Address(address)
263
+
264
+ method = "get_account_state"
265
+ return t.cast(
266
+ t.Tuple[t.Optional[Account], t.Optional[ShardAccount]],
267
+ await self._adnl_call(method, address),
268
+ )