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
|
@@ -7,22 +7,34 @@ from contextlib import suppress
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from itertools import cycle
|
|
9
9
|
|
|
10
|
-
from pytoniq_core import Address, Transaction
|
|
10
|
+
from pytoniq_core import Address, BlockIdExt, Block, Transaction
|
|
11
11
|
|
|
12
12
|
from tonutils.clients.adnl.client import AdnlClient
|
|
13
13
|
from tonutils.clients.adnl.provider import AdnlProvider
|
|
14
|
-
from tonutils.clients.adnl.provider.config import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
from tonutils.clients.adnl.provider.config import (
|
|
15
|
+
get_mainnet_global_config,
|
|
16
|
+
get_testnet_global_config,
|
|
17
|
+
)
|
|
18
|
+
from tonutils.clients.adnl.provider.models import GlobalConfig, MasterchainInfo
|
|
19
|
+
from tonutils.clients.adnl.utils import decode_stack, encode_stack
|
|
18
20
|
from tonutils.clients.base import BaseClient
|
|
21
|
+
from tonutils.clients.limiter import RateLimiter
|
|
19
22
|
from tonutils.exceptions import (
|
|
20
|
-
AdnlBalancerConnectionError,
|
|
21
|
-
ClientNotConnectedError,
|
|
22
|
-
RateLimitExceededError,
|
|
23
23
|
ClientError,
|
|
24
|
+
BalancerError,
|
|
25
|
+
RunGetMethodError,
|
|
26
|
+
ProviderResponseError,
|
|
27
|
+
TransportError,
|
|
28
|
+
ProviderError,
|
|
29
|
+
ProviderTimeoutError,
|
|
30
|
+
)
|
|
31
|
+
from tonutils.types import (
|
|
32
|
+
ClientType,
|
|
33
|
+
ContractStateInfo,
|
|
34
|
+
NetworkGlobalID,
|
|
35
|
+
RetryPolicy,
|
|
36
|
+
WorkchainID,
|
|
24
37
|
)
|
|
25
|
-
from tonutils.types import ClientType, ContractStateInfo, NetworkGlobalID
|
|
26
38
|
|
|
27
39
|
_T = t.TypeVar("_T")
|
|
28
40
|
|
|
@@ -55,7 +67,8 @@ class AdnlBalancer(BaseClient):
|
|
|
55
67
|
*,
|
|
56
68
|
network: NetworkGlobalID = NetworkGlobalID.MAINNET,
|
|
57
69
|
clients: t.List[AdnlClient],
|
|
58
|
-
connect_timeout:
|
|
70
|
+
connect_timeout: float = 2.0,
|
|
71
|
+
request_timeout: float = 12.0,
|
|
59
72
|
) -> None:
|
|
60
73
|
"""
|
|
61
74
|
Initialize ADNL balancer.
|
|
@@ -70,7 +83,9 @@ class AdnlBalancer(BaseClient):
|
|
|
70
83
|
|
|
71
84
|
:param network: Target TON network (mainnet or testnet)
|
|
72
85
|
:param clients: List of AdnlClient instances to balance between
|
|
73
|
-
:param connect_timeout: Timeout in seconds for connect/reconnect
|
|
86
|
+
:param connect_timeout: Timeout in seconds for connect/reconnect attempts
|
|
87
|
+
:param request_timeout: Maximum total time in seconds for a balancer operation,
|
|
88
|
+
including all failover attempts across providers
|
|
74
89
|
"""
|
|
75
90
|
self.network: NetworkGlobalID = network
|
|
76
91
|
|
|
@@ -79,7 +94,9 @@ class AdnlBalancer(BaseClient):
|
|
|
79
94
|
self.__init_clients(clients)
|
|
80
95
|
|
|
81
96
|
self._rr = cycle(self._clients)
|
|
97
|
+
|
|
82
98
|
self._connect_timeout = connect_timeout
|
|
99
|
+
self._request_timeout = request_timeout
|
|
83
100
|
|
|
84
101
|
self._health_interval = 5.5
|
|
85
102
|
self._health_task: t.Optional[asyncio.Task] = None
|
|
@@ -109,6 +126,25 @@ class AdnlBalancer(BaseClient):
|
|
|
109
126
|
self._clients.append(client)
|
|
110
127
|
self._states.append(state)
|
|
111
128
|
|
|
129
|
+
@property
|
|
130
|
+
def provider(self) -> AdnlProvider:
|
|
131
|
+
"""
|
|
132
|
+
Provider of the currently selected ADNL client.
|
|
133
|
+
|
|
134
|
+
:return: AdnlProvider instance of chosen client
|
|
135
|
+
"""
|
|
136
|
+
c = self._pick_client()
|
|
137
|
+
return c.provider
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def is_connected(self) -> bool:
|
|
141
|
+
"""
|
|
142
|
+
Check whether at least one underlying ADNL client is connected.
|
|
143
|
+
|
|
144
|
+
:return: True if any client is connected, otherwise False
|
|
145
|
+
"""
|
|
146
|
+
return any(c.is_connected for c in self._clients)
|
|
147
|
+
|
|
112
148
|
@property
|
|
113
149
|
def clients(self) -> t.Tuple[AdnlClient, ...]:
|
|
114
150
|
"""
|
|
@@ -148,28 +184,6 @@ class AdnlBalancer(BaseClient):
|
|
|
148
184
|
or (state.retry_after is not None and state.retry_after > now)
|
|
149
185
|
)
|
|
150
186
|
|
|
151
|
-
@property
|
|
152
|
-
def provider(self) -> AdnlProvider:
|
|
153
|
-
"""
|
|
154
|
-
Provider of the currently selected ADNL client.
|
|
155
|
-
|
|
156
|
-
:return: AdnlProvider instance of chosen client
|
|
157
|
-
:raises ClientNotConnectedError: If no clients are connected
|
|
158
|
-
"""
|
|
159
|
-
if not self.is_connected:
|
|
160
|
-
raise ClientNotConnectedError(self)
|
|
161
|
-
c = self._pick_client()
|
|
162
|
-
return c.provider
|
|
163
|
-
|
|
164
|
-
@property
|
|
165
|
-
def is_connected(self) -> bool:
|
|
166
|
-
"""
|
|
167
|
-
Check whether at least one underlying ADNL client is connected.
|
|
168
|
-
|
|
169
|
-
:return: True if any client is connected, otherwise False
|
|
170
|
-
"""
|
|
171
|
-
return any(c.is_connected for c in self._clients)
|
|
172
|
-
|
|
173
187
|
async def __aenter__(self) -> AdnlBalancer:
|
|
174
188
|
"""
|
|
175
189
|
Enter async context manager and connect underlying clients.
|
|
@@ -195,12 +209,14 @@ class AdnlBalancer(BaseClient):
|
|
|
195
209
|
*,
|
|
196
210
|
network: NetworkGlobalID = NetworkGlobalID.MAINNET,
|
|
197
211
|
config: t.Union[GlobalConfig, t.Dict[str, t.Any]],
|
|
198
|
-
|
|
199
|
-
|
|
212
|
+
connect_timeout: float = 2.0,
|
|
213
|
+
request_timeout: float = 12.0,
|
|
214
|
+
client_connect_timeout: float = 1.5,
|
|
215
|
+
client_request_timeout: float = 5.0,
|
|
200
216
|
rps_limit: t.Optional[int] = None,
|
|
201
217
|
rps_period: float = 1.0,
|
|
202
|
-
|
|
203
|
-
|
|
218
|
+
rps_per_client: bool = False,
|
|
219
|
+
retry_policy: t.Optional[RetryPolicy] = None,
|
|
204
220
|
) -> AdnlBalancer:
|
|
205
221
|
"""
|
|
206
222
|
Build ADNL balancer from a lite-server config.
|
|
@@ -214,64 +230,71 @@ class AdnlBalancer(BaseClient):
|
|
|
214
230
|
|
|
215
231
|
:param network: Target TON network
|
|
216
232
|
:param config: GlobalConfig instance or raw dict
|
|
217
|
-
:param
|
|
218
|
-
|
|
219
|
-
:param
|
|
233
|
+
:param connect_timeout: Timeout in seconds for a single connect/reconnect attempt
|
|
234
|
+
performed by the balancer during failover.
|
|
235
|
+
:param request_timeout: Maximum total time in seconds for a single balancer operation,
|
|
236
|
+
including all failover attempts across clients.
|
|
237
|
+
:param client_connect_timeout: Timeout in seconds for connect/handshake performed by an
|
|
238
|
+
individual ADNL client.
|
|
239
|
+
:param client_request_timeout: Timeout in seconds for a single request executed by an
|
|
240
|
+
individual ADNL client.
|
|
241
|
+
:param rps_limit: Optional requests-per-second limit
|
|
220
242
|
:param rps_period: Time window in seconds for RPS limit
|
|
221
|
-
:param
|
|
222
|
-
:param
|
|
243
|
+
:param rps_per_client: Whether to create per-client limiters
|
|
244
|
+
:param retry_policy: Optional retry policy that defines per-error-code retry rules
|
|
223
245
|
:return: Configured AdnlBalancer instance
|
|
224
246
|
"""
|
|
225
247
|
if isinstance(config, dict):
|
|
226
248
|
config = GlobalConfig(**config)
|
|
227
249
|
|
|
228
|
-
shared_limiter: t.Optional[
|
|
229
|
-
if rps_limit is not None and not
|
|
230
|
-
shared_limiter =
|
|
250
|
+
shared_limiter: t.Optional[RateLimiter] = None
|
|
251
|
+
if rps_limit is not None and not rps_per_client:
|
|
252
|
+
shared_limiter = RateLimiter(rps_limit, rps_period)
|
|
231
253
|
|
|
232
254
|
clients: t.List[AdnlClient] = []
|
|
233
255
|
for node in config.liteservers:
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
256
|
+
limiter = (
|
|
257
|
+
RateLimiter(rps_limit, rps_period)
|
|
258
|
+
if rps_per_client and rps_limit is not None
|
|
259
|
+
else shared_limiter
|
|
260
|
+
)
|
|
261
|
+
client_rps_limit = rps_limit if rps_per_client else None
|
|
262
|
+
|
|
263
|
+
clients.append(
|
|
264
|
+
AdnlClient(
|
|
265
|
+
network=network,
|
|
266
|
+
ip=node.host,
|
|
267
|
+
port=node.port,
|
|
268
|
+
public_key=node.id,
|
|
269
|
+
connect_timeout=client_connect_timeout,
|
|
270
|
+
request_timeout=client_request_timeout,
|
|
271
|
+
rps_limit=client_rps_limit,
|
|
272
|
+
rps_period=rps_period,
|
|
273
|
+
limiter=limiter,
|
|
274
|
+
retry_policy=retry_policy,
|
|
239
275
|
)
|
|
240
|
-
_rps_limit = rps_limit
|
|
241
|
-
else:
|
|
242
|
-
_limiter = shared_limiter
|
|
243
|
-
_rps_limit = None
|
|
244
|
-
|
|
245
|
-
client = AdnlClient(
|
|
246
|
-
network=network,
|
|
247
|
-
ip=node.host,
|
|
248
|
-
port=node.port,
|
|
249
|
-
public_key=node.id,
|
|
250
|
-
timeout=timeout,
|
|
251
|
-
rps_limit=_rps_limit,
|
|
252
|
-
rps_retries=rps_retries,
|
|
253
|
-
rps_period=rps_period,
|
|
254
|
-
limiter=_limiter,
|
|
255
276
|
)
|
|
256
|
-
clients.append(client)
|
|
257
277
|
|
|
258
278
|
return cls(
|
|
259
279
|
network=network,
|
|
260
280
|
clients=clients,
|
|
261
281
|
connect_timeout=connect_timeout,
|
|
282
|
+
request_timeout=request_timeout,
|
|
262
283
|
)
|
|
263
284
|
|
|
264
285
|
@classmethod
|
|
265
|
-
|
|
286
|
+
def from_network_config(
|
|
266
287
|
cls,
|
|
267
288
|
*,
|
|
268
289
|
network: NetworkGlobalID = NetworkGlobalID.MAINNET,
|
|
269
|
-
|
|
270
|
-
|
|
290
|
+
connect_timeout: float = 2.0,
|
|
291
|
+
request_timeout: float = 12.0,
|
|
292
|
+
client_connect_timeout: float = 1.5,
|
|
293
|
+
client_request_timeout: float = 5.0,
|
|
271
294
|
rps_limit: t.Optional[int] = None,
|
|
272
295
|
rps_period: float = 1.0,
|
|
273
|
-
|
|
274
|
-
|
|
296
|
+
rps_per_client: bool = False,
|
|
297
|
+
retry_policy: t.Optional[RetryPolicy] = None,
|
|
275
298
|
) -> AdnlBalancer:
|
|
276
299
|
"""
|
|
277
300
|
Build ADNL balancer using global config fetched from ton.org.
|
|
@@ -284,30 +307,36 @@ class AdnlBalancer(BaseClient):
|
|
|
284
307
|
- dTON telegram bot: https://t.me/dtontech_bot (https://dton.io/)
|
|
285
308
|
|
|
286
309
|
:param network: Target TON network
|
|
287
|
-
:param
|
|
288
|
-
|
|
289
|
-
:param
|
|
310
|
+
:param connect_timeout: Timeout in seconds for a single connect/reconnect attempt
|
|
311
|
+
performed by the balancer during failover.
|
|
312
|
+
:param request_timeout: Maximum total time in seconds for a single balancer operation,
|
|
313
|
+
including all failover attempts across clients.
|
|
314
|
+
:param client_connect_timeout: Timeout in seconds for connect/handshake performed by an
|
|
315
|
+
individual ADNL client.
|
|
316
|
+
:param client_request_timeout: Timeout in seconds for a single request executed by an
|
|
317
|
+
individual ADNL client.
|
|
318
|
+
:param rps_limit: Optional requests-per-second limit
|
|
290
319
|
:param rps_period: Time window in seconds for RPS limit
|
|
291
|
-
:param
|
|
292
|
-
:param
|
|
320
|
+
:param rps_per_client: Whether to create per-client limiters
|
|
321
|
+
:param retry_policy: Optional retry policy that defines per-error-code retry rules
|
|
293
322
|
:return: Configured AdnlBalancer instance
|
|
294
323
|
"""
|
|
295
|
-
ton_client = TONClient()
|
|
296
324
|
config_getters = {
|
|
297
|
-
NetworkGlobalID.MAINNET:
|
|
298
|
-
NetworkGlobalID.TESTNET:
|
|
325
|
+
NetworkGlobalID.MAINNET: get_mainnet_global_config,
|
|
326
|
+
NetworkGlobalID.TESTNET: get_testnet_global_config,
|
|
299
327
|
}
|
|
300
|
-
|
|
301
|
-
config = await config_getters[network]()
|
|
328
|
+
config = config_getters[network]()
|
|
302
329
|
return cls.from_config(
|
|
303
330
|
network=network,
|
|
304
331
|
config=config,
|
|
305
|
-
|
|
332
|
+
connect_timeout=connect_timeout,
|
|
333
|
+
request_timeout=request_timeout,
|
|
334
|
+
client_connect_timeout=client_connect_timeout,
|
|
335
|
+
client_request_timeout=client_request_timeout,
|
|
306
336
|
rps_limit=rps_limit,
|
|
307
337
|
rps_period=rps_period,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
connect_timeout=connect_timeout,
|
|
338
|
+
rps_per_client=rps_per_client,
|
|
339
|
+
retry_policy=retry_policy,
|
|
311
340
|
)
|
|
312
341
|
|
|
313
342
|
def _pick_client(self) -> AdnlClient:
|
|
@@ -319,10 +348,10 @@ class AdnlBalancer(BaseClient):
|
|
|
319
348
|
- minimal ping RTT and age among same-height clients
|
|
320
349
|
- round-robin fallback if no height information
|
|
321
350
|
"""
|
|
322
|
-
|
|
351
|
+
alive = list(self.alive_clients)
|
|
323
352
|
|
|
324
|
-
if not
|
|
325
|
-
raise
|
|
353
|
+
if not alive:
|
|
354
|
+
raise BalancerError("no alive lite-server clients available")
|
|
326
355
|
|
|
327
356
|
height_candidates: t.List[
|
|
328
357
|
t.Tuple[
|
|
@@ -333,7 +362,7 @@ class AdnlBalancer(BaseClient):
|
|
|
333
362
|
]
|
|
334
363
|
] = []
|
|
335
364
|
|
|
336
|
-
for client in
|
|
365
|
+
for client in alive:
|
|
337
366
|
mc_block = client.provider.last_mc_block
|
|
338
367
|
if mc_block is None:
|
|
339
368
|
continue
|
|
@@ -357,10 +386,10 @@ class AdnlBalancer(BaseClient):
|
|
|
357
386
|
|
|
358
387
|
for _ in range(len(self._clients)):
|
|
359
388
|
candidate = next(self._rr)
|
|
360
|
-
if candidate in
|
|
389
|
+
if candidate in alive and candidate.is_connected:
|
|
361
390
|
return candidate
|
|
362
391
|
|
|
363
|
-
return
|
|
392
|
+
return alive[0]
|
|
364
393
|
|
|
365
394
|
def _mark_success(self, client: AdnlClient) -> None:
|
|
366
395
|
"""
|
|
@@ -413,45 +442,58 @@ class AdnlBalancer(BaseClient):
|
|
|
413
442
|
:param func: Callable performing an operation using an AdnlProvider
|
|
414
443
|
:return: Result of the successful invocation
|
|
415
444
|
"""
|
|
416
|
-
last_exc: t.Optional[BaseException] = None
|
|
417
445
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
446
|
+
async def _run() -> _T:
|
|
447
|
+
last_exc: t.Optional[BaseException] = None
|
|
448
|
+
|
|
449
|
+
for _ in range(len(self._clients)):
|
|
450
|
+
if not self.alive_clients:
|
|
451
|
+
break
|
|
421
452
|
|
|
422
|
-
|
|
453
|
+
client = self._pick_client()
|
|
454
|
+
|
|
455
|
+
if not client.provider.is_connected:
|
|
456
|
+
try:
|
|
457
|
+
await asyncio.wait_for(
|
|
458
|
+
client.provider.reconnect(),
|
|
459
|
+
timeout=self._connect_timeout,
|
|
460
|
+
)
|
|
461
|
+
except Exception as e:
|
|
462
|
+
self._mark_error(client, is_rate_limit=False)
|
|
463
|
+
last_exc = e
|
|
464
|
+
continue
|
|
423
465
|
|
|
424
|
-
if not client.provider.is_connected:
|
|
425
466
|
try:
|
|
426
|
-
await
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
except
|
|
467
|
+
result = await func(client.provider)
|
|
468
|
+
|
|
469
|
+
except RunGetMethodError:
|
|
470
|
+
raise
|
|
471
|
+
except ProviderResponseError as e:
|
|
472
|
+
is_rate_limit = e.code in {228, 5556}
|
|
473
|
+
self._mark_error(client, is_rate_limit=is_rate_limit)
|
|
474
|
+
last_exc = e
|
|
475
|
+
continue
|
|
476
|
+
except (TransportError, ProviderError) as e:
|
|
431
477
|
self._mark_error(client, is_rate_limit=False)
|
|
432
478
|
last_exc = e
|
|
433
479
|
continue
|
|
434
480
|
|
|
435
|
-
|
|
436
|
-
result
|
|
437
|
-
except RateLimitExceededError as e:
|
|
438
|
-
self._mark_error(client, is_rate_limit=True)
|
|
439
|
-
last_exc = e
|
|
440
|
-
continue
|
|
441
|
-
except Exception as e:
|
|
442
|
-
self._mark_error(client, is_rate_limit=False)
|
|
443
|
-
last_exc = e
|
|
444
|
-
continue
|
|
481
|
+
self._mark_success(client)
|
|
482
|
+
return result
|
|
445
483
|
|
|
446
|
-
|
|
447
|
-
|
|
484
|
+
if last_exc is not None:
|
|
485
|
+
raise last_exc
|
|
448
486
|
|
|
449
|
-
|
|
450
|
-
raise last_exc
|
|
487
|
+
raise BalancerError("all lite-server providers failed to process request")
|
|
451
488
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
489
|
+
try:
|
|
490
|
+
return await asyncio.wait_for(_run(), timeout=self._request_timeout)
|
|
491
|
+
except asyncio.TimeoutError as exc:
|
|
492
|
+
raise ProviderTimeoutError(
|
|
493
|
+
timeout=self._request_timeout,
|
|
494
|
+
endpoint="adnl balancer",
|
|
495
|
+
operation="failover request",
|
|
496
|
+
) from exc
|
|
455
497
|
|
|
456
498
|
async def _send_boc(self, boc: str) -> None:
|
|
457
499
|
async def _call(provider: AdnlProvider) -> None:
|
|
@@ -486,12 +528,12 @@ class AdnlBalancer(BaseClient):
|
|
|
486
528
|
|
|
487
529
|
curr_lt = state.last_transaction_lt
|
|
488
530
|
curr_hash = state.last_transaction_hash
|
|
489
|
-
transactions:
|
|
531
|
+
transactions: t.List[Transaction] = []
|
|
490
532
|
|
|
491
533
|
while len(transactions) < limit and curr_lt != 0:
|
|
492
534
|
batch_size = min(16, limit - len(transactions))
|
|
493
535
|
|
|
494
|
-
async def _call(provider: AdnlProvider) ->
|
|
536
|
+
async def _call(provider: AdnlProvider) -> t.List[Transaction]:
|
|
495
537
|
return await provider.get_transactions(
|
|
496
538
|
account=account,
|
|
497
539
|
count=batch_size,
|
|
@@ -504,7 +546,7 @@ class AdnlBalancer(BaseClient):
|
|
|
504
546
|
break
|
|
505
547
|
|
|
506
548
|
if to_lt > 0 and txs[-1].lt <= to_lt:
|
|
507
|
-
trimmed:
|
|
549
|
+
trimmed: t.List[Transaction] = []
|
|
508
550
|
for tx in txs:
|
|
509
551
|
if tx.lt <= to_lt:
|
|
510
552
|
break
|
|
@@ -541,6 +583,12 @@ class AdnlBalancer(BaseClient):
|
|
|
541
583
|
return await self._with_failover(_call)
|
|
542
584
|
|
|
543
585
|
def _ensure_health_task(self) -> None:
|
|
586
|
+
"""
|
|
587
|
+
Ensure background health check task is running.
|
|
588
|
+
|
|
589
|
+
Starts a periodic reconnect loop for unavailable clients
|
|
590
|
+
if it is not already active.
|
|
591
|
+
"""
|
|
544
592
|
if self._health_task is not None and not self._health_task.done():
|
|
545
593
|
return
|
|
546
594
|
|
|
@@ -551,6 +599,11 @@ class AdnlBalancer(BaseClient):
|
|
|
551
599
|
)
|
|
552
600
|
|
|
553
601
|
async def _health_loop(self) -> None:
|
|
602
|
+
"""
|
|
603
|
+
Periodically attempt to reconnect dead ADNL clients.
|
|
604
|
+
|
|
605
|
+
Runs until cancelled.
|
|
606
|
+
"""
|
|
554
607
|
|
|
555
608
|
async def _recon(c: AdnlClient) -> None:
|
|
556
609
|
with suppress(Exception):
|
|
@@ -590,9 +643,7 @@ class AdnlBalancer(BaseClient):
|
|
|
590
643
|
self._ensure_health_task()
|
|
591
644
|
return
|
|
592
645
|
|
|
593
|
-
raise
|
|
594
|
-
"All lite-servers failed to establish connection."
|
|
595
|
-
)
|
|
646
|
+
raise BalancerError("all lite-servers failed to establish connection")
|
|
596
647
|
|
|
597
648
|
async def close(self) -> None:
|
|
598
649
|
task, self._health_task = self._health_task, None
|
|
@@ -604,3 +655,146 @@ class AdnlBalancer(BaseClient):
|
|
|
604
655
|
|
|
605
656
|
tasks = [client.close() for client in self._clients]
|
|
606
657
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
658
|
+
|
|
659
|
+
async def get_time(self) -> int:
|
|
660
|
+
"""
|
|
661
|
+
Fetch current network time from lite-server.
|
|
662
|
+
|
|
663
|
+
:return: Current UNIX timestamp
|
|
664
|
+
"""
|
|
665
|
+
|
|
666
|
+
async def _call(provider: AdnlProvider) -> int:
|
|
667
|
+
return await provider.get_time()
|
|
668
|
+
|
|
669
|
+
return await self._with_failover(_call)
|
|
670
|
+
|
|
671
|
+
async def get_version(self) -> int:
|
|
672
|
+
"""
|
|
673
|
+
Fetch lite-server protocol version.
|
|
674
|
+
|
|
675
|
+
:return: Version number
|
|
676
|
+
"""
|
|
677
|
+
|
|
678
|
+
async def _call(provider: AdnlProvider) -> int:
|
|
679
|
+
return await provider.get_version()
|
|
680
|
+
|
|
681
|
+
return await self._with_failover(_call)
|
|
682
|
+
|
|
683
|
+
async def wait_masterchain_seqno(
|
|
684
|
+
self,
|
|
685
|
+
seqno: int,
|
|
686
|
+
timeout_ms: int,
|
|
687
|
+
schema_name: str,
|
|
688
|
+
data: t.Optional[dict] = None,
|
|
689
|
+
) -> dict:
|
|
690
|
+
"""
|
|
691
|
+
Combine waitMasterchainSeqno with another lite-server query.
|
|
692
|
+
|
|
693
|
+
:param seqno: Masterchain seqno to wait for
|
|
694
|
+
:param timeout_ms: Wait timeout in milliseconds
|
|
695
|
+
:param schema_name: Lite-server TL method name without prefix
|
|
696
|
+
:param data: Additional method arguments
|
|
697
|
+
:return: Lite-server response as dictionary
|
|
698
|
+
"""
|
|
699
|
+
|
|
700
|
+
async def _call(provider: AdnlProvider) -> dict:
|
|
701
|
+
return await provider.wait_masterchain_seqno(
|
|
702
|
+
seqno=seqno,
|
|
703
|
+
timeout_ms=timeout_ms,
|
|
704
|
+
schema_name=schema_name,
|
|
705
|
+
data=data,
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
return await self._with_failover(_call)
|
|
709
|
+
|
|
710
|
+
async def get_masterchain_info(self) -> MasterchainInfo:
|
|
711
|
+
"""
|
|
712
|
+
Fetch basic masterchain information.
|
|
713
|
+
|
|
714
|
+
:return: MasterchainInfo instance
|
|
715
|
+
"""
|
|
716
|
+
|
|
717
|
+
async def _call(provider: AdnlProvider) -> MasterchainInfo:
|
|
718
|
+
return await provider.get_masterchain_info()
|
|
719
|
+
|
|
720
|
+
return await self._with_failover(_call)
|
|
721
|
+
|
|
722
|
+
async def lookup_block(
|
|
723
|
+
self,
|
|
724
|
+
workchain: WorkchainID,
|
|
725
|
+
shard: int,
|
|
726
|
+
seqno: int = -1,
|
|
727
|
+
lt: t.Optional[int] = None,
|
|
728
|
+
utime: t.Optional[int] = None,
|
|
729
|
+
) -> t.Tuple[BlockIdExt, Block]:
|
|
730
|
+
"""
|
|
731
|
+
Locate a block by workchain/shard and one of seqno/lt/utime.
|
|
732
|
+
|
|
733
|
+
:param workchain: Workchain identifier
|
|
734
|
+
:param shard: Shard identifier
|
|
735
|
+
:param seqno: Block seqno or -1 to ignore
|
|
736
|
+
:param lt: Logical time filter
|
|
737
|
+
:param utime: UNIX time filter
|
|
738
|
+
:return: Tuple of BlockIdExt and deserialized Block
|
|
739
|
+
"""
|
|
740
|
+
|
|
741
|
+
async def _call(provider: AdnlProvider) -> t.Tuple[BlockIdExt, Block]:
|
|
742
|
+
return await provider.lookup_block(
|
|
743
|
+
workchain=workchain,
|
|
744
|
+
shard=shard,
|
|
745
|
+
seqno=seqno,
|
|
746
|
+
lt=lt,
|
|
747
|
+
utime=utime,
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
return await self._with_failover(_call)
|
|
751
|
+
|
|
752
|
+
async def get_block_header(
|
|
753
|
+
self,
|
|
754
|
+
block: BlockIdExt,
|
|
755
|
+
) -> t.Tuple[BlockIdExt, Block]:
|
|
756
|
+
"""
|
|
757
|
+
Fetch and deserialize block header by BlockIdExt.
|
|
758
|
+
|
|
759
|
+
:param block: BlockIdExt to query
|
|
760
|
+
:return: Tuple of BlockIdExt and deserialized Block
|
|
761
|
+
"""
|
|
762
|
+
|
|
763
|
+
async def _call(provider: AdnlProvider) -> t.Tuple[BlockIdExt, Block]:
|
|
764
|
+
return await provider.get_block_header(block)
|
|
765
|
+
|
|
766
|
+
return await self._with_failover(_call)
|
|
767
|
+
|
|
768
|
+
async def get_block_transactions_ext(
|
|
769
|
+
self,
|
|
770
|
+
block: BlockIdExt,
|
|
771
|
+
count: int = 1024,
|
|
772
|
+
) -> t.List[Transaction]:
|
|
773
|
+
"""
|
|
774
|
+
Fetch extended block transactions list.
|
|
775
|
+
|
|
776
|
+
:param block: Target block identifier
|
|
777
|
+
:param count: Maximum number of transactions per request
|
|
778
|
+
:return: List of deserialized Transaction objects
|
|
779
|
+
"""
|
|
780
|
+
|
|
781
|
+
async def _call(provider: AdnlProvider) -> t.List[Transaction]:
|
|
782
|
+
return await provider.get_block_transactions_ext(block, count=count)
|
|
783
|
+
|
|
784
|
+
return await self._with_failover(_call)
|
|
785
|
+
|
|
786
|
+
async def get_all_shards_info(
|
|
787
|
+
self,
|
|
788
|
+
block: t.Optional[BlockIdExt] = None,
|
|
789
|
+
) -> t.List[BlockIdExt]:
|
|
790
|
+
"""
|
|
791
|
+
Fetch shard info for all workchains at a given masterchain block.
|
|
792
|
+
|
|
793
|
+
:param block: Masterchain block ID or None to use latest
|
|
794
|
+
:return: List of shard BlockIdExt objects
|
|
795
|
+
"""
|
|
796
|
+
|
|
797
|
+
async def _call(provider: AdnlProvider) -> t.List[BlockIdExt]:
|
|
798
|
+
return await provider.get_all_shards_info(block)
|
|
799
|
+
|
|
800
|
+
return await self._with_failover(_call)
|