tonutils 2.0.1b1__py3-none-any.whl → 2.0.1b3__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 (79) hide show
  1. tonutils/__init__.py +0 -2
  2. tonutils/__meta__.py +1 -1
  3. tonutils/clients/__init__.py +5 -9
  4. tonutils/clients/adnl/__init__.py +5 -1
  5. tonutils/clients/adnl/balancer.py +319 -125
  6. tonutils/clients/adnl/client.py +187 -51
  7. tonutils/clients/adnl/provider/config.py +19 -25
  8. tonutils/clients/adnl/provider/models.py +4 -0
  9. tonutils/clients/adnl/provider/provider.py +191 -145
  10. tonutils/clients/adnl/provider/transport.py +38 -32
  11. tonutils/clients/adnl/provider/workers/base.py +0 -2
  12. tonutils/clients/adnl/provider/workers/pinger.py +1 -1
  13. tonutils/clients/adnl/provider/workers/reader.py +3 -2
  14. tonutils/clients/adnl/{provider/builder.py → utils.py} +62 -2
  15. tonutils/clients/http/__init__.py +11 -8
  16. tonutils/clients/http/balancer.py +75 -63
  17. tonutils/clients/http/clients/__init__.py +13 -0
  18. tonutils/clients/http/clients/chainstack.py +48 -0
  19. tonutils/clients/http/clients/quicknode.py +47 -0
  20. tonutils/clients/http/clients/tatum.py +56 -0
  21. tonutils/clients/http/{tonapi/client.py → clients/tonapi.py} +31 -31
  22. tonutils/clients/http/{toncenter/client.py → clients/toncenter.py} +59 -48
  23. tonutils/clients/http/providers/__init__.py +4 -0
  24. tonutils/clients/http/providers/base.py +201 -0
  25. tonutils/clients/http/providers/response.py +85 -0
  26. tonutils/clients/http/providers/tonapi/__init__.py +3 -0
  27. tonutils/clients/http/{tonapi → providers/tonapi}/models.py +1 -0
  28. tonutils/clients/http/providers/tonapi/provider.py +125 -0
  29. tonutils/clients/http/providers/toncenter/__init__.py +3 -0
  30. tonutils/clients/http/{toncenter → providers/toncenter}/models.py +1 -0
  31. tonutils/clients/http/providers/toncenter/provider.py +119 -0
  32. tonutils/clients/http/utils.py +140 -0
  33. tonutils/clients/limiter.py +115 -0
  34. tonutils/contracts/__init__.py +18 -0
  35. tonutils/contracts/base.py +33 -20
  36. tonutils/contracts/dns/methods.py +2 -2
  37. tonutils/contracts/jetton/methods.py +2 -2
  38. tonutils/contracts/nft/methods.py +2 -2
  39. tonutils/contracts/nft/tlb.py +1 -1
  40. tonutils/{protocols/contract.py → contracts/protocol.py} +29 -29
  41. tonutils/contracts/telegram/methods.py +2 -2
  42. tonutils/contracts/vanity/__init__.py +17 -0
  43. tonutils/contracts/vanity/models.py +39 -0
  44. tonutils/contracts/vanity/tlb.py +40 -0
  45. tonutils/contracts/vanity/vanity.py +40 -0
  46. tonutils/contracts/wallet/__init__.py +2 -0
  47. tonutils/contracts/wallet/base.py +3 -3
  48. tonutils/contracts/wallet/messages.py +1 -1
  49. tonutils/contracts/wallet/methods.py +2 -2
  50. tonutils/{protocols/wallet.py → contracts/wallet/protocol.py} +35 -35
  51. tonutils/contracts/wallet/versions/v5.py +3 -3
  52. tonutils/exceptions.py +134 -226
  53. tonutils/types.py +115 -0
  54. tonutils/utils.py +3 -3
  55. {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/METADATA +4 -4
  56. tonutils-2.0.1b3.dist-info/RECORD +93 -0
  57. tonutils/clients/adnl/provider/limiter.py +0 -56
  58. tonutils/clients/adnl/stack.py +0 -64
  59. tonutils/clients/http/chainstack/__init__.py +0 -4
  60. tonutils/clients/http/chainstack/client.py +0 -63
  61. tonutils/clients/http/chainstack/provider.py +0 -44
  62. tonutils/clients/http/quicknode/__init__.py +0 -4
  63. tonutils/clients/http/quicknode/client.py +0 -60
  64. tonutils/clients/http/quicknode/provider.py +0 -42
  65. tonutils/clients/http/tatum/__init__.py +0 -4
  66. tonutils/clients/http/tatum/client.py +0 -66
  67. tonutils/clients/http/tatum/provider.py +0 -53
  68. tonutils/clients/http/tonapi/__init__.py +0 -4
  69. tonutils/clients/http/tonapi/provider.py +0 -150
  70. tonutils/clients/http/tonapi/stack.py +0 -71
  71. tonutils/clients/http/toncenter/__init__.py +0 -4
  72. tonutils/clients/http/toncenter/provider.py +0 -145
  73. tonutils/clients/http/toncenter/stack.py +0 -73
  74. tonutils/protocols/__init__.py +0 -9
  75. tonutils-2.0.1b1.dist-info/RECORD +0 -94
  76. /tonutils/{protocols/client.py → clients/protocol.py} +0 -0
  77. {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/WHEEL +0 -0
  78. {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/licenses/LICENSE +0 -0
  79. {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/top_level.txt +0 -0
@@ -18,12 +18,6 @@ from pytoniq_core import (
18
18
  from pytoniq_core.crypto.ciphers import get_random
19
19
  from pytoniq_core.crypto.crc import crc16
20
20
 
21
- from tonutils.clients.adnl.provider.builder import (
22
- build_contract_state_info,
23
- build_shard_account,
24
- build_config_all,
25
- )
26
- from tonutils.clients.adnl.provider.limiter import PriorityLimiter
27
21
  from tonutils.clients.adnl.provider.models import LiteServer, MasterchainInfo
28
22
  from tonutils.clients.adnl.provider.transport import AdnlTcpTransport
29
23
  from tonutils.clients.adnl.provider.workers import (
@@ -31,35 +25,38 @@ from tonutils.clients.adnl.provider.workers import (
31
25
  ReaderWorker,
32
26
  UpdaterWorker,
33
27
  )
28
+ from tonutils.clients.adnl.utils import (
29
+ build_contract_state_info,
30
+ build_shard_account,
31
+ build_config_all,
32
+ )
33
+ from tonutils.clients.limiter import RateLimiter
34
34
  from tonutils.exceptions import (
35
- AdnlHandshakeError,
36
- AdnlProviderConnectError,
37
- AdnlServerError,
38
- AdnlProviderMissingBlockError,
39
- AdnlProviderResponseError,
40
- AdnlProviderClosedError,
41
35
  ClientError,
42
- ClientNotConnectedError,
43
- RateLimitExceededError,
36
+ NotConnectedError,
37
+ ProviderError,
44
38
  RunGetMethodError,
39
+ ProviderTimeoutError,
40
+ RetryLimitError,
41
+ ProviderResponseError,
42
+ )
43
+ from tonutils.types import (
44
+ ContractStateInfo,
45
+ RetryPolicy,
46
+ WorkchainID,
45
47
  )
46
- from tonutils.types import ContractStateInfo, WorkchainID
47
48
 
48
49
 
49
50
  class AdnlProvider:
50
- """
51
- ADNL-based provider for TON lite-servers.
52
-
53
- Handles encrypted TCP transport, ping and masterchain updates, and exposes
54
- a high-level API for lite-server queries.
55
- """
51
+ """ADNL-based provider for TON lite-servers."""
56
52
 
57
53
  def __init__(
58
54
  self,
59
55
  node: LiteServer,
60
- timeout: int = 10,
61
- rps_retries: t.Optional[int] = None,
62
- limiter: t.Optional[PriorityLimiter] = None,
56
+ connect_timeout: float = 2.0,
57
+ request_timeout: float = 10.0,
58
+ limiter: t.Optional[RateLimiter] = None,
59
+ retry_policy: t.Optional[RetryPolicy] = None,
63
60
  ) -> None:
64
61
  """
65
62
  Initialize ADNL provider for a specific lite-server.
@@ -71,14 +68,15 @@ class AdnlProvider:
71
68
  - dTON telegram bot: https://t.me/dtontech_bot (https://dton.io/)
72
69
 
73
70
  :param node: LiteServer configuration (host, port, public key)
74
- :param timeout: Timeout in seconds for queries
75
- :param rps_retries: Number of retries on rate limiting
71
+ :param connect_timeout: Timeout in seconds for connect/reconnect
72
+ :param request_timeout: Timeout in seconds for queries
76
73
  :param limiter: Optional priority-aware rate limiter
74
+ :param retry_policy: Optional retry policy that defines per-error-code retry rules
77
75
  """
78
76
  self.node = node
79
- self.timeout = timeout
80
- self.rps_retries = rps_retries
81
- self.transport = AdnlTcpTransport(self.node, self.timeout)
77
+ self.connect_timeout = connect_timeout
78
+ self.request_timeout = request_timeout
79
+ self.transport = AdnlTcpTransport(self.node, self.connect_timeout)
82
80
  self.loop: t.Optional[asyncio.AbstractEventLoop] = None
83
81
 
84
82
  self.tl_schemas = TlGenerator.with_default_schemas().generate()
@@ -92,16 +90,13 @@ class AdnlProvider:
92
90
 
93
91
  self.pending: t.Dict[str, asyncio.Future] = {}
94
92
 
93
+ self._limiter: t.Optional[RateLimiter] = limiter
94
+ self._retry_policy: t.Optional[RetryPolicy] = retry_policy
95
95
  self._connect_lock: asyncio.Lock = asyncio.Lock()
96
- self._limiter: t.Optional[PriorityLimiter] = limiter
97
96
 
98
97
  @property
99
98
  def is_connected(self) -> bool:
100
- """
101
- Check whether the underlying transport is connected.
102
-
103
- :return: True if ADNL transport is connected, False otherwise
104
- """
99
+ """Check whether the underlying transport is connected."""
105
100
  return self.transport.is_connected
106
101
 
107
102
  @property
@@ -172,18 +167,8 @@ class AdnlProvider:
172
167
  self.loop = asyncio.get_running_loop()
173
168
  try:
174
169
  await self.transport.connect()
175
- except AdnlHandshakeError as exc:
176
- raise AdnlProviderConnectError(
177
- host=self.node.host,
178
- port=self.node.port,
179
- message=str(exc),
180
- ) from exc
181
- except OSError as exc:
182
- raise AdnlProviderConnectError(
183
- host=self.node.host,
184
- port=self.node.port,
185
- message=str(exc),
186
- ) from exc
170
+ except Exception as exc:
171
+ raise ProviderError(f"adnl connect failed ({self.node.endpoint})") from exc
187
172
 
188
173
  tasks = [self.reader.start(), self.pinger.start(), self.updater.start()]
189
174
  await asyncio.gather(*tasks, return_exceptions=True)
@@ -221,124 +206,99 @@ class AdnlProvider:
221
206
  async with self._connect_lock:
222
207
  await self._do_close()
223
208
 
224
- async def send_adnl_query(
225
- self,
226
- query: bytes,
227
- priority: bool = False,
228
- ) -> dict:
209
+ async def _send_once_adnl_query(self, query: bytes, *, priority: bool) -> dict:
229
210
  """
230
- Send a raw ADNL query with retry support for rate-limit and missing-block conditions.
231
-
232
- Retries temporary overload errors (228, 5556) and missing-block errors (651)
233
- before returning a response or raising an exception.
211
+ Send a single ADNL query.
234
212
 
235
213
  :param query: Encoded ADNL TL-query bytes
236
214
  :param priority: Whether to use priority slot in the limiter
237
215
  :return: Lite-server response payload as a decoded dictionary
238
216
  """
239
- return await self._send_with_missing_block_retries(query, priority)
217
+ if not self.is_connected or self.loop is None:
218
+ raise NotConnectedError()
240
219
 
241
- async def _send_with_missing_block_retries(
242
- self,
243
- query: bytes,
244
- priority: bool = False,
245
- ) -> dict:
246
- """
247
- Internal wrapper adding retry handling for missing-block errors (code 651).
220
+ if self._limiter is not None:
221
+ await self._limiter.acquire(priority=priority)
248
222
 
249
- Performs a fixed number of attempts with exponential backoff on 651 and
250
- delegates transport-level and rate-limit handling to `_send_with_rps_retries`.
223
+ query_id = get_random(32)
224
+ packet = self.tl_schemas.serialize(
225
+ self.adnl_query_tl_schema,
226
+ {"query_id": query_id, "query": query},
227
+ )
251
228
 
252
- :param query: Encoded ADNL TL-query bytes
253
- :param priority: Whether to use priority slot in the limiter
254
- :return: Lite-server response payload as a dictionary
255
- """
256
- max_651_retries = 5
257
- error_message = "unknown lite-server error"
229
+ query_id_key = query_id[::-1].hex()
230
+ fut: asyncio.Future[t.Any] = self.loop.create_future()
231
+ self.pending[query_id_key] = fut
232
+
233
+ try:
234
+ await self.transport.send_adnl_packet(packet)
258
235
 
259
- for attempt in range(max_651_retries):
260
236
  try:
261
- return await self._send_with_rps_retries(query, priority=priority)
262
- except AdnlServerError as e:
263
- if e.code != 651:
264
- raise
265
- error_message = e.message
266
- if attempt < max_651_retries - 1:
267
- await asyncio.sleep(0.3 * (2**attempt))
268
- continue
269
- break
270
-
271
- raise AdnlProviderMissingBlockError(
272
- attempts=max_651_retries,
273
- host=self.node.host,
274
- port=self.node.port,
275
- message=error_message,
276
- )
237
+ resp = await asyncio.wait_for(fut, timeout=self.request_timeout)
238
+ except asyncio.TimeoutError as exc:
239
+ raise ProviderTimeoutError(
240
+ timeout=self.request_timeout,
241
+ endpoint=self.node.endpoint,
242
+ operation="adnl query",
243
+ ) from exc
244
+ except asyncio.CancelledError as exc:
245
+ raise ProviderError(
246
+ f"adnl provider closed ({self.node.endpoint})"
247
+ ) from exc
248
+
249
+ if not isinstance(resp, dict):
250
+ raise ProviderError(
251
+ f"adnl invalid response type ({self.node.endpoint})"
252
+ )
253
+
254
+ return resp
255
+ finally:
256
+ if query_id_key in self.pending:
257
+ del self.pending[query_id_key]
277
258
 
278
- async def _send_with_rps_retries(
259
+ async def send_adnl_query(
279
260
  self,
280
261
  query: bytes,
281
262
  priority: bool = False,
282
263
  ) -> dict:
283
264
  """
284
- Internal ADNL request executor with retry handling for rate-limit errors.
265
+ Send a raw ADNL query with retry handling based on retry policy.
285
266
 
286
- Performs a bounded number of attempts on lite-server overload conditions
287
- (error codes 228 and 5556) using exponential backoff before failing.
267
+ On server error, retries the request according to the rule associated
268
+ with the received error code. If no rule matches, or retry attempts are
269
+ exhausted, the error is raised.
288
270
 
289
271
  :param query: Encoded ADNL TL-query bytes
290
272
  :param priority: Whether to use priority slot in the limiter
291
- :return: Lite-server response payload as a dictionary
273
+ :return: Lite-server response payload as a decoded dictionary
292
274
  """
293
- if not self.is_connected or self.loop is None:
294
- raise ClientNotConnectedError(self)
295
-
296
- attempts = max(self.rps_retries or 0, 1)
297
- for attempt in range(attempts):
298
- if self._limiter is not None:
299
- await self._limiter.acquire(priority=priority)
300
-
301
- query_id = get_random(32)
302
- packet = self.tl_schemas.serialize(
303
- self.adnl_query_tl_schema,
304
- {
305
- "query_id": query_id,
306
- "query": query,
307
- },
308
- )
309
- query_id_key = query_id[::-1].hex()
310
- fut: asyncio.Future = self.loop.create_future()
311
- self.pending[query_id_key] = fut
275
+ attempts: t.Dict[int, int] = {}
312
276
 
277
+ while True:
313
278
  try:
314
- await self.transport.send_adnl_packet(packet)
315
- try:
316
- resp = await asyncio.wait_for(fut, timeout=self.timeout)
317
- except asyncio.TimeoutError:
279
+ return await self._send_once_adnl_query(query, priority=priority)
280
+
281
+ except ProviderResponseError as e:
282
+ policy = self._retry_policy
283
+ if policy is None:
318
284
  raise
319
- except asyncio.CancelledError as exc:
320
- raise AdnlProviderClosedError(
321
- host=self.node.host,
322
- port=self.node.port,
323
- ) from exc
324
- except AdnlServerError as e:
325
- if e.code in (228, 5556):
326
- if attempt < attempts - 1:
327
- await asyncio.sleep(0.3 * (2**attempt))
328
- continue
329
- break
285
+
286
+ rule = policy.rule_for(e.code, e.message)
287
+ if rule is None:
330
288
  raise
331
- if not isinstance(resp, dict):
332
- raise AdnlProviderResponseError(
333
- host=self.node.host,
334
- port=self.node.port,
335
- )
336
- return resp
337
- finally:
338
- if query_id_key in self.pending:
339
- del self.pending[query_id_key]
340
-
341
- raise RateLimitExceededError(attempts)
289
+
290
+ key = id(rule)
291
+ n = attempts.get(key, 0) + 1
292
+ attempts[key] = n
293
+
294
+ if n >= rule.attempts:
295
+ raise RetryLimitError(
296
+ attempts=n,
297
+ max_attempts=rule.attempts,
298
+ last_error=e,
299
+ ) from e
300
+
301
+ await asyncio.sleep(rule.delay(n - 1))
342
302
 
343
303
  async def send_liteserver_query(
344
304
  self,
@@ -507,6 +467,92 @@ class AdnlProvider:
507
467
  Block.deserialize(cell[0].begin_parse()),
508
468
  )
509
469
 
470
+ async def get_block_header(
471
+ self,
472
+ block: BlockIdExt,
473
+ *,
474
+ priority: bool = False,
475
+ ) -> t.Tuple[BlockIdExt, Block]:
476
+ """
477
+ Fetch and deserialize block header by BlockIdExt.
478
+
479
+ :param block: BlockIdExt to query
480
+ :param priority: Whether to use priority slot in the limiter
481
+ :return: Tuple of BlockIdExt and deserialized Block
482
+ """
483
+ result = await self.send_liteserver_query(
484
+ method="getBlockHeader",
485
+ data={"id": block.to_dict(), "mode": 0},
486
+ priority=priority,
487
+ )
488
+
489
+ cell = Cell.one_from_boc(result["header_proof"])
490
+ return (
491
+ BlockIdExt.from_dict(result["id"]),
492
+ Block.deserialize(cell[0].begin_parse()),
493
+ )
494
+
495
+ async def get_block_transactions_ext(
496
+ self,
497
+ block: BlockIdExt,
498
+ *,
499
+ count: int = 1024,
500
+ priority: bool = False,
501
+ ) -> t.List[Transaction]:
502
+ """
503
+ Fetch extended block transactions list.
504
+
505
+ :param block: Target block identifier
506
+ :param count: Maximum number of transactions per request
507
+ :param priority: Whether to use priority slot in the limiter
508
+ :return: List of deserialized Transaction objects
509
+ """
510
+ mode = 39
511
+
512
+ result = await self.send_liteserver_query(
513
+ method="listBlockTransactionsExt",
514
+ data={
515
+ "id": block.to_dict(),
516
+ "mode": mode,
517
+ "count": count,
518
+ "want_proof": b"",
519
+ },
520
+ priority=priority,
521
+ )
522
+
523
+ transactions: t.List[Transaction] = []
524
+
525
+ def _append(result_data: dict) -> None:
526
+ if not result_data.get("transactions"):
527
+ return
528
+ for cell in Cell.from_boc(result_data["transactions"]):
529
+ transactions.append(Transaction.deserialize(cell.begin_parse()))
530
+
531
+ _append(result)
532
+
533
+ while result.get("incomplete"):
534
+ mode = 167
535
+ after = {
536
+ "account": transactions[-1].account_addr_hex,
537
+ "lt": transactions[-1].lt,
538
+ }
539
+
540
+ result = await self.send_liteserver_query(
541
+ method="listBlockTransactionsExt",
542
+ data={
543
+ "id": block.to_dict(),
544
+ "mode": mode,
545
+ "count": count,
546
+ "want_proof": b"",
547
+ "after": after,
548
+ },
549
+ priority=priority,
550
+ )
551
+
552
+ _append(result)
553
+
554
+ return transactions
555
+
510
556
  async def get_all_shards_info(
511
557
  self,
512
558
  block: t.Optional[BlockIdExt] = None,
@@ -586,13 +632,13 @@ class AdnlProvider:
586
632
 
587
633
  exit_code = result.get("exit_code")
588
634
  if exit_code is None:
589
- raise AdnlServerError(
590
- code=-1,
591
- message="RunGetMethod: missing `exit_code` in response",
635
+ raise ProviderError(
636
+ f"runSmcMethod: missing `exit_code` in response ({self.node.endpoint})"
592
637
  )
638
+
593
639
  if exit_code != 0:
594
640
  raise RunGetMethodError(
595
- address=address,
641
+ address=address.to_str(),
596
642
  method_name=method_name,
597
643
  exit_code=exit_code,
598
644
  )
@@ -676,7 +722,7 @@ class AdnlProvider:
676
722
  from_hash: str,
677
723
  *,
678
724
  priority: bool = False,
679
- ) -> list[Transaction]:
725
+ ) -> t.List[Transaction]:
680
726
  """
681
727
  Fetch a chain of transactions for an account.
682
728
 
@@ -707,7 +753,7 @@ class AdnlProvider:
707
753
  cells = Cell.from_boc(result["transactions"])
708
754
 
709
755
  prev_tr_hash = from_hash
710
- transactions: list[Transaction] = []
756
+ transactions: t.List[Transaction] = []
711
757
 
712
758
  for i, cell in enumerate(cells):
713
759
  curr_hash = cell.get_hash(0).hex()
@@ -16,13 +16,7 @@ from pytoniq_core.crypto.ciphers import (
16
16
  )
17
17
 
18
18
  from tonutils.clients.adnl.provider.models import LiteServer
19
- from tonutils.exceptions import (
20
- AdnlHandshakeError,
21
- AdnlTransportCipherError,
22
- AdnlTransportFrameError,
23
- AdnlTransportStateError,
24
- AdnlTransportError,
25
- )
19
+ from tonutils.exceptions import TransportError
26
20
 
27
21
 
28
22
  class AdnlTcpTransport:
@@ -40,12 +34,12 @@ class AdnlTcpTransport:
40
34
  for incoming frames.
41
35
  """
42
36
 
43
- def __init__(self, node: LiteServer, timeout: int) -> None:
37
+ def __init__(self, node: LiteServer, connect_timeout: float) -> None:
44
38
  """
45
39
  Initialize ADNL TCP transport for a liteserver connection.
46
40
 
47
41
  :param node: Liteserver configuration with host, port, and public key
48
- :param timeout: Timeout in seconds for connection and I/O operations
42
+ :param connect_timeout: Timeout in seconds for connection
49
43
  """
50
44
  self.server = Server(
51
45
  host=node.host,
@@ -54,7 +48,7 @@ class AdnlTcpTransport:
54
48
  )
55
49
  self.client = Client(Client.generate_ed25519_private_key())
56
50
 
57
- self.timeout = timeout
51
+ self.connect_timeout = connect_timeout
58
52
  self.enc_cipher = None
59
53
  self.dec_cipher = None
60
54
 
@@ -131,12 +125,12 @@ class AdnlTcpTransport:
131
125
  async def _flush(self) -> None:
132
126
  """Flush the TCP write buffer."""
133
127
  if self.writer is None:
134
- raise AdnlTransportStateError("`writer` is not initialized")
128
+ raise TransportError("transport state: writer is not initialized")
135
129
  try:
136
130
  await self.writer.drain()
137
- except ConnectionError:
131
+ except ConnectionError as exc:
138
132
  await self.close()
139
- raise
133
+ raise TransportError(f"transport io: drain failed") from exc
140
134
 
141
135
  def encrypt_frame(self, data: bytes) -> bytes:
142
136
  """
@@ -145,7 +139,9 @@ class AdnlTcpTransport:
145
139
  :param data: Plaintext frame bytes
146
140
  """
147
141
  if self.enc_cipher is None:
148
- raise AdnlTransportCipherError("`encryption`")
142
+ raise TransportError(
143
+ "transport cipher: encryption cipher is not initialized"
144
+ )
149
145
  return aes_ctr_encrypt(self.enc_cipher, data)
150
146
 
151
147
  def decrypt_frame(self, data: bytes) -> bytes:
@@ -155,22 +151,32 @@ class AdnlTcpTransport:
155
151
  :param data: Encrypted frame bytes
156
152
  """
157
153
  if self.dec_cipher is None:
158
- raise AdnlTransportCipherError("`decryption`")
154
+ raise TransportError(
155
+ "transport cipher: decryption cipher is not initialized"
156
+ )
159
157
  return aes_ctr_decrypt(self.dec_cipher, data)
160
158
 
161
159
  async def connect(self) -> None:
162
160
  """Establish encrypted connection to the liteserver."""
163
161
  if self._connected:
164
- raise AdnlTransportStateError("already connected")
162
+ raise TransportError("transport state: already connected")
165
163
 
166
164
  self.loop = asyncio.get_running_loop()
167
165
 
168
- self.reader, self.writer = await asyncio.wait_for(
169
- asyncio.open_connection(self.server.host, self.server.port),
170
- timeout=self.timeout,
171
- )
166
+ try:
167
+ self.reader, self.writer = await asyncio.wait_for(
168
+ asyncio.open_connection(self.server.host, self.server.port),
169
+ timeout=self.connect_timeout,
170
+ )
171
+ except asyncio.TimeoutError as exc:
172
+ raise TransportError(
173
+ f"transport connect: timeout {self.connect_timeout}s"
174
+ ) from exc
175
+ except OSError as exc:
176
+ raise TransportError(f"transport connect: failed") from exc
177
+
172
178
  if self.writer is None or self.reader is None:
173
- raise AdnlTransportError("failed to initialize TCP streams")
179
+ raise TransportError(f"transport connect: failed to initialize streams")
174
180
 
175
181
  try:
176
182
  handshake = self._build_handshake()
@@ -180,15 +186,15 @@ class AdnlTcpTransport:
180
186
  try:
181
187
  await asyncio.wait_for(
182
188
  self.read_frame(discard=True),
183
- timeout=self.timeout,
189
+ timeout=self.connect_timeout,
184
190
  )
185
191
  except asyncio.IncompleteReadError as exc:
186
- raise AdnlHandshakeError(
187
- "ADNL handshake failed: remote closed connection"
192
+ raise TransportError(
193
+ f"transport handshake: remote closed connection"
188
194
  ) from exc
189
195
  except asyncio.TimeoutError as exc:
190
- raise AdnlHandshakeError(
191
- f"Timed out waiting for initial ADNL handshake ({self.timeout}s)"
196
+ raise TransportError(
197
+ f"transport handshake: timeout {self.connect_timeout}s"
192
198
  ) from exc
193
199
  self._connected = True
194
200
  self._reader_task = asyncio.create_task(
@@ -208,7 +214,7 @@ class AdnlTcpTransport:
208
214
  :param payload: Raw ADNL packet bytes
209
215
  """
210
216
  if not self._connected or self.writer is None:
211
- raise AdnlTransportStateError("transport is not connected")
217
+ raise TransportError("transport state: not connected")
212
218
 
213
219
  packet = self._build_frame(payload)
214
220
  encrypted = self.encrypt_frame(packet)
@@ -223,7 +229,7 @@ class AdnlTcpTransport:
223
229
  Blocks until a complete packet is available from the background reader.
224
230
  """
225
231
  if not self._connected:
226
- raise AdnlTransportStateError("transport is not connected")
232
+ raise TransportError("transport state: not connected")
227
233
  return await self._incoming.get()
228
234
 
229
235
  async def read_frame(self, discard: bool = False) -> t.Optional[bytes]:
@@ -238,24 +244,24 @@ class AdnlTcpTransport:
238
244
  :param discard: If True, validates but returns None (used for handshake ack)
239
245
  """
240
246
  if self.reader is None:
241
- raise AdnlTransportStateError("`reader` is not initialized")
247
+ raise TransportError("transport state: reader is not initialized")
242
248
 
243
249
  length_enc = await self.reader.readexactly(4)
244
250
  length_dec = self.decrypt_frame(length_enc)
245
251
  data_len = int.from_bytes(length_dec, "little")
246
252
 
247
253
  if data_len <= 0:
248
- raise AdnlTransportFrameError(f"non-positive length `{data_len}`")
254
+ raise TransportError(f"transport frame: non-positive length {data_len}")
249
255
 
250
256
  data_enc = await self.reader.readexactly(data_len)
251
257
  data = self.decrypt_frame(data_enc)
252
258
 
253
259
  if len(data) < 32:
254
- raise AdnlTransportFrameError("frame is too short")
260
+ raise TransportError("transport frame: frame is too short")
255
261
 
256
262
  payload, checksum = data[:-32], data[-32:]
257
263
  if hashlib.sha256(payload).digest() != checksum:
258
- raise AdnlTransportFrameError("checksum mismatch")
264
+ raise TransportError("transport frame: checksum mismatch")
259
265
 
260
266
  if discard:
261
267
  return None
@@ -63,8 +63,6 @@ class BaseWorker(ABC):
63
63
  await self._run()
64
64
  except asyncio.CancelledError:
65
65
  pass
66
- except (Exception,):
67
- pass
68
66
  finally:
69
67
  self._running = False
70
68
 
@@ -66,7 +66,7 @@ class PingerWorker(BaseWorker):
66
66
  await self.provider.transport.send_adnl_packet(payload)
67
67
 
68
68
  start = self.provider.loop.time()
69
- await asyncio.wait_for(fut, timeout=self.provider.timeout)
69
+ await asyncio.wait_for(fut, timeout=self.provider.request_timeout)
70
70
  end = self.provider.loop.time()
71
71
 
72
72
  self._last_time = end
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from tonutils.clients.adnl.provider.workers.base import BaseWorker
4
- from tonutils.exceptions import AdnlServerError
4
+ from tonutils.exceptions import ProviderResponseError
5
5
 
6
6
 
7
7
  class ReaderWorker(BaseWorker):
@@ -35,9 +35,10 @@ class ReaderWorker(BaseWorker):
35
35
  payload = root.get("answer", root)
36
36
 
37
37
  if "code" in payload and "message" in payload:
38
- exception = AdnlServerError(
38
+ exception = ProviderResponseError(
39
39
  code=payload["code"],
40
40
  message=payload["message"],
41
+ endpoint=self.provider.node.endpoint,
41
42
  )
42
43
  fut.set_exception(exception)
43
44
  else: