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.
- tonutils/__init__.py +0 -2
- tonutils/__meta__.py +1 -1
- tonutils/clients/__init__.py +5 -9
- tonutils/clients/adnl/__init__.py +5 -1
- tonutils/clients/adnl/balancer.py +319 -125
- tonutils/clients/adnl/client.py +187 -51
- tonutils/clients/adnl/provider/config.py +19 -25
- tonutils/clients/adnl/provider/models.py +4 -0
- tonutils/clients/adnl/provider/provider.py +191 -145
- tonutils/clients/adnl/provider/transport.py +38 -32
- tonutils/clients/adnl/provider/workers/base.py +0 -2
- tonutils/clients/adnl/provider/workers/pinger.py +1 -1
- tonutils/clients/adnl/provider/workers/reader.py +3 -2
- tonutils/clients/adnl/{provider/builder.py → utils.py} +62 -2
- tonutils/clients/http/__init__.py +11 -8
- tonutils/clients/http/balancer.py +75 -63
- tonutils/clients/http/clients/__init__.py +13 -0
- tonutils/clients/http/clients/chainstack.py +48 -0
- tonutils/clients/http/clients/quicknode.py +47 -0
- tonutils/clients/http/clients/tatum.py +56 -0
- tonutils/clients/http/{tonapi/client.py → clients/tonapi.py} +31 -31
- tonutils/clients/http/{toncenter/client.py → clients/toncenter.py} +59 -48
- tonutils/clients/http/providers/__init__.py +4 -0
- tonutils/clients/http/providers/base.py +201 -0
- tonutils/clients/http/providers/response.py +85 -0
- tonutils/clients/http/providers/tonapi/__init__.py +3 -0
- tonutils/clients/http/{tonapi → providers/tonapi}/models.py +1 -0
- tonutils/clients/http/providers/tonapi/provider.py +125 -0
- tonutils/clients/http/providers/toncenter/__init__.py +3 -0
- tonutils/clients/http/{toncenter → providers/toncenter}/models.py +1 -0
- tonutils/clients/http/providers/toncenter/provider.py +119 -0
- tonutils/clients/http/utils.py +140 -0
- tonutils/clients/limiter.py +115 -0
- tonutils/contracts/__init__.py +18 -0
- tonutils/contracts/base.py +33 -20
- tonutils/contracts/dns/methods.py +2 -2
- tonutils/contracts/jetton/methods.py +2 -2
- tonutils/contracts/nft/methods.py +2 -2
- tonutils/contracts/nft/tlb.py +1 -1
- tonutils/{protocols/contract.py → contracts/protocol.py} +29 -29
- tonutils/contracts/telegram/methods.py +2 -2
- tonutils/contracts/vanity/__init__.py +17 -0
- tonutils/contracts/vanity/models.py +39 -0
- tonutils/contracts/vanity/tlb.py +40 -0
- tonutils/contracts/vanity/vanity.py +40 -0
- tonutils/contracts/wallet/__init__.py +2 -0
- tonutils/contracts/wallet/base.py +3 -3
- tonutils/contracts/wallet/messages.py +1 -1
- tonutils/contracts/wallet/methods.py +2 -2
- tonutils/{protocols/wallet.py → contracts/wallet/protocol.py} +35 -35
- tonutils/contracts/wallet/versions/v5.py +3 -3
- tonutils/exceptions.py +134 -226
- tonutils/types.py +115 -0
- tonutils/utils.py +3 -3
- {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/METADATA +4 -4
- tonutils-2.0.1b3.dist-info/RECORD +93 -0
- tonutils/clients/adnl/provider/limiter.py +0 -56
- tonutils/clients/adnl/stack.py +0 -64
- tonutils/clients/http/chainstack/__init__.py +0 -4
- tonutils/clients/http/chainstack/client.py +0 -63
- tonutils/clients/http/chainstack/provider.py +0 -44
- tonutils/clients/http/quicknode/__init__.py +0 -4
- tonutils/clients/http/quicknode/client.py +0 -60
- tonutils/clients/http/quicknode/provider.py +0 -42
- tonutils/clients/http/tatum/__init__.py +0 -4
- tonutils/clients/http/tatum/client.py +0 -66
- tonutils/clients/http/tatum/provider.py +0 -53
- tonutils/clients/http/tonapi/__init__.py +0 -4
- tonutils/clients/http/tonapi/provider.py +0 -150
- tonutils/clients/http/tonapi/stack.py +0 -71
- tonutils/clients/http/toncenter/__init__.py +0 -4
- tonutils/clients/http/toncenter/provider.py +0 -145
- tonutils/clients/http/toncenter/stack.py +0 -73
- tonutils/protocols/__init__.py +0 -9
- tonutils-2.0.1b1.dist-info/RECORD +0 -94
- /tonutils/{protocols/client.py → clients/protocol.py} +0 -0
- {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/WHEEL +0 -0
- {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
limiter: t.Optional[
|
|
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
|
|
75
|
-
:param
|
|
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.
|
|
80
|
-
self.
|
|
81
|
-
self.transport = AdnlTcpTransport(self.node, self.
|
|
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
|
|
176
|
-
raise
|
|
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
|
|
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
|
|
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
|
-
|
|
217
|
+
if not self.is_connected or self.loop is None:
|
|
218
|
+
raise NotConnectedError()
|
|
240
219
|
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
253
|
-
:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
262
|
-
except
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
259
|
+
async def send_adnl_query(
|
|
279
260
|
self,
|
|
280
261
|
query: bytes,
|
|
281
262
|
priority: bool = False,
|
|
282
263
|
) -> dict:
|
|
283
264
|
"""
|
|
284
|
-
|
|
265
|
+
Send a raw ADNL query with retry handling based on retry policy.
|
|
285
266
|
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|
590
|
-
|
|
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
|
-
) ->
|
|
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:
|
|
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,
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
162
|
+
raise TransportError("transport state: already connected")
|
|
165
163
|
|
|
166
164
|
self.loop = asyncio.get_running_loop()
|
|
167
165
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
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.
|
|
189
|
+
timeout=self.connect_timeout,
|
|
184
190
|
)
|
|
185
191
|
except asyncio.IncompleteReadError as exc:
|
|
186
|
-
raise
|
|
187
|
-
"
|
|
192
|
+
raise TransportError(
|
|
193
|
+
f"transport handshake: remote closed connection"
|
|
188
194
|
) from exc
|
|
189
195
|
except asyncio.TimeoutError as exc:
|
|
190
|
-
raise
|
|
191
|
-
f"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
264
|
+
raise TransportError("transport frame: checksum mismatch")
|
|
259
265
|
|
|
260
266
|
if discard:
|
|
261
267
|
return None
|
|
@@ -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.
|
|
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
|
|
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 =
|
|
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:
|