tonutils 2.0.1b6__py3-none-any.whl → 2.0.1b8__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 (47) hide show
  1. tonutils/__init__.py +8 -13
  2. tonutils/cli.py +1 -1
  3. tonutils/clients/adnl/balancer.py +132 -355
  4. tonutils/clients/adnl/client.py +32 -202
  5. tonutils/clients/adnl/mixin.py +268 -0
  6. tonutils/clients/adnl/provider/config.py +7 -20
  7. tonutils/clients/adnl/provider/provider.py +61 -16
  8. tonutils/clients/adnl/provider/transport.py +13 -4
  9. tonutils/clients/adnl/provider/workers/pinger.py +1 -1
  10. tonutils/clients/adnl/utils.py +5 -5
  11. tonutils/clients/base.py +52 -92
  12. tonutils/clients/http/balancer.py +93 -90
  13. tonutils/clients/http/clients/tatum.py +1 -0
  14. tonutils/clients/http/clients/tonapi.py +12 -24
  15. tonutils/clients/http/clients/toncenter.py +15 -33
  16. tonutils/clients/http/provider/base.py +75 -60
  17. tonutils/clients/http/provider/models.py +1 -1
  18. tonutils/clients/http/provider/tonapi.py +0 -5
  19. tonutils/clients/http/provider/toncenter.py +4 -8
  20. tonutils/clients/protocol.py +6 -6
  21. tonutils/contracts/__init__.py +3 -0
  22. tonutils/contracts/base.py +32 -32
  23. tonutils/contracts/protocol.py +9 -9
  24. tonutils/contracts/telegram/tlb.py +1 -1
  25. tonutils/contracts/wallet/__init__.py +4 -0
  26. tonutils/contracts/wallet/base.py +5 -5
  27. tonutils/contracts/wallet/tlb.py +18 -16
  28. tonutils/contracts/wallet/versions/v5.py +6 -6
  29. tonutils/exceptions.py +45 -102
  30. tonutils/tools/block_scanner/__init__.py +5 -1
  31. tonutils/tools/block_scanner/scanner.py +1 -1
  32. tonutils/tools/status_monitor/monitor.py +6 -6
  33. tonutils/types.py +24 -10
  34. tonutils/utils.py +47 -7
  35. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/METADATA +3 -20
  36. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/RECORD +40 -46
  37. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/WHEEL +1 -1
  38. tonutils/__meta__.py +0 -1
  39. tonutils/tonconnect/__init__.py +0 -0
  40. tonutils/tonconnect/bridge/__init__.py +0 -0
  41. tonutils/tonconnect/events.py +0 -0
  42. tonutils/tonconnect/models/__init__.py +0 -0
  43. tonutils/tonconnect/storage.py +0 -0
  44. tonutils/tonconnect/tonconnect.py +0 -0
  45. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/entry_points.txt +0 -0
  46. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/licenses/LICENSE +0 -0
  47. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/top_level.txt +0 -0
tonutils/__init__.py CHANGED
@@ -1,17 +1,12 @@
1
- from . import (
2
- clients,
3
- contracts,
4
- exceptions,
5
- types,
6
- utils,
7
- )
8
- from .__meta__ import __version__
1
+ # Copyright (c) 2024 Shon Ness
2
+ #
3
+ # This source code is licensed under the MIT License found in the
4
+ # LICENSE file in the root directory of this source tree.
9
5
 
10
6
  __all__ = [
7
+ "__uri__",
11
8
  "__version__",
12
- "clients",
13
- "contracts",
14
- "exceptions",
15
- "types",
16
- "utils",
17
9
  ]
10
+
11
+ __version__ = "2.0.1b8"
12
+ __uri__ = "https://github.com/nessshon/tonutils"
tonutils/cli.py CHANGED
@@ -2,7 +2,7 @@ import argparse
2
2
  import asyncio
3
3
  import typing as t
4
4
 
5
- from tonutils.__meta__ import __version__
5
+ from tonutils import __version__
6
6
  from tonutils.clients.adnl.provider.config import (
7
7
  get_mainnet_global_config,
8
8
  get_testnet_global_config,
@@ -7,22 +7,22 @@ from contextlib import suppress
7
7
  from dataclasses import dataclass
8
8
  from itertools import cycle
9
9
 
10
- from pytoniq_core import Address, BlockIdExt, Block, Transaction
11
-
12
10
  from tonutils.clients.adnl.client import LiteClient
11
+ from tonutils.clients.adnl.mixin import LiteMixin
13
12
  from tonutils.clients.adnl.provider import AdnlProvider
14
13
  from tonutils.clients.adnl.provider.config import (
15
14
  get_mainnet_global_config,
16
15
  get_testnet_global_config,
16
+ load_global_config,
17
17
  )
18
- from tonutils.clients.adnl.provider.models import GlobalConfig, MasterchainInfo
19
- from tonutils.clients.adnl.utils import decode_stack, encode_stack
18
+ from tonutils.clients.adnl.provider.models import GlobalConfig
20
19
  from tonutils.clients.base import BaseClient
21
20
  from tonutils.clients.limiter import RateLimiter
22
21
  from tonutils.exceptions import (
23
22
  ClientError,
24
23
  BalancerError,
25
24
  RunGetMethodError,
25
+ NotConnectedError,
26
26
  ProviderResponseError,
27
27
  TransportError,
28
28
  ProviderError,
@@ -30,10 +30,8 @@ from tonutils.exceptions import (
30
30
  )
31
31
  from tonutils.types import (
32
32
  ClientType,
33
- ContractStateInfo,
34
33
  NetworkGlobalID,
35
34
  RetryPolicy,
36
- WorkchainID,
37
35
  )
38
36
 
39
37
  _T = t.TypeVar("_T")
@@ -52,7 +50,7 @@ class LiteClientState:
52
50
  error_count: int = 0
53
51
 
54
52
 
55
- class LiteBalancer(BaseClient):
53
+ class LiteBalancer(LiteMixin, BaseClient):
56
54
  """
57
55
  Multi-client lite-server balancer with automatic failover and load balancing.
58
56
 
@@ -91,7 +89,7 @@ class LiteBalancer(BaseClient):
91
89
 
92
90
  self._clients: t.List[LiteClient] = []
93
91
  self._states: t.List[LiteClientState] = []
94
- self.__init_clients(clients)
92
+ self._init_clients(clients)
95
93
 
96
94
  self._rr = cycle(self._clients)
97
95
 
@@ -104,28 +102,6 @@ class LiteBalancer(BaseClient):
104
102
  self._retry_after_base = 1.0
105
103
  self._retry_after_max = 10.0
106
104
 
107
- def __init_clients(
108
- self,
109
- clients: t.List[LiteClient],
110
- ) -> None:
111
- """
112
- Validate and register input lite-server clients.
113
-
114
- Ensures correct client type and network assignment.
115
- """
116
- for client in clients:
117
- if client.TYPE != ClientType.ADNL:
118
- raise ClientError(
119
- "LiteBalancer can work only with LiteClient instances, "
120
- f"got {client.__class__.__name__}."
121
- )
122
-
123
- client.network = self.network
124
-
125
- state = LiteClientState(client=client)
126
- self._clients.append(client)
127
- self._states.append(state)
128
-
129
105
  @property
130
106
  def provider(self) -> AdnlProvider:
131
107
  """
@@ -137,13 +113,13 @@ class LiteBalancer(BaseClient):
137
113
  return c.provider
138
114
 
139
115
  @property
140
- def is_connected(self) -> bool:
116
+ def connected(self) -> bool:
141
117
  """
142
118
  Check whether at least one underlying lite-server client is connected.
143
119
 
144
120
  :return: True if any client is connected, otherwise False
145
121
  """
146
- return any(c.is_connected for c in self._clients)
122
+ return any(c.connected for c in self._clients)
147
123
 
148
124
  @property
149
125
  def clients(self) -> t.Tuple[LiteClient, ...]:
@@ -165,7 +141,7 @@ class LiteBalancer(BaseClient):
165
141
  return tuple(
166
142
  state.client
167
143
  for state in self._states
168
- if state.client.is_connected
144
+ if state.client.connected
169
145
  and (state.retry_after is None or state.retry_after <= now)
170
146
  )
171
147
 
@@ -180,35 +156,16 @@ class LiteBalancer(BaseClient):
180
156
  return tuple(
181
157
  state.client
182
158
  for state in self._states
183
- if not state.client.is_connected
159
+ if not state.client.connected
184
160
  or (state.retry_after is not None and state.retry_after > now)
185
161
  )
186
162
 
187
- async def __aenter__(self) -> LiteBalancer:
188
- """
189
- Enter async context manager and connect underlying clients.
190
-
191
- :return: Self instance with initialized connections
192
- """
193
- await self.connect()
194
- return self
195
-
196
- async def __aexit__(
197
- self,
198
- exc_type: t.Optional[t.Type[BaseException]],
199
- exc_value: t.Optional[BaseException],
200
- traceback: t.Optional[t.Any],
201
- ) -> None:
202
- """Exit async context manager and close all underlying clients."""
203
- with suppress(asyncio.CancelledError):
204
- await self.close()
205
-
206
163
  @classmethod
207
164
  def from_config(
208
165
  cls,
209
166
  network: NetworkGlobalID = NetworkGlobalID.MAINNET,
210
167
  *,
211
- config: t.Union[GlobalConfig, t.Dict[str, t.Any]],
168
+ config: t.Union[GlobalConfig, t.Dict[str, t.Any], str],
212
169
  connect_timeout: float = 2.0,
213
170
  request_timeout: float = 12.0,
214
171
  client_connect_timeout: float = 1.5,
@@ -229,7 +186,7 @@ class LiteBalancer(BaseClient):
229
186
  Public free configs may also be used via `from_network_config()`.
230
187
 
231
188
  :param network: Target TON network
232
- :param config: GlobalConfig instance or raw dict
189
+ :param config: GlobalConfig instance, config file path as string, or raw dict
233
190
  :param connect_timeout: Timeout in seconds for a single connect/reconnect attempt
234
191
  performed by the balancer during failover.
235
192
  :param request_timeout: Maximum total time in seconds for a single balancer operation,
@@ -244,6 +201,8 @@ class LiteBalancer(BaseClient):
244
201
  :param retry_policy: Optional retry policy that defines per-error-code retry rules
245
202
  :return: Configured LiteBalancer instance
246
203
  """
204
+ if isinstance(config, str):
205
+ config = load_global_config(config)
247
206
  if isinstance(config, dict):
248
207
  config = GlobalConfig(**config)
249
208
 
@@ -339,6 +298,60 @@ class LiteBalancer(BaseClient):
339
298
  retry_policy=retry_policy,
340
299
  )
341
300
 
301
+ async def connect(self) -> None:
302
+ if self.connected:
303
+ self._ensure_health_task()
304
+ return
305
+
306
+ async def _con(client: LiteClient) -> None:
307
+ with suppress(Exception):
308
+ await asyncio.wait_for(
309
+ client.connect(),
310
+ timeout=self._connect_timeout,
311
+ )
312
+
313
+ tasks = [_con(client) for client in self._clients]
314
+ await asyncio.gather(*tasks, return_exceptions=True)
315
+
316
+ if self.connected:
317
+ self._ensure_health_task()
318
+ return
319
+
320
+ raise BalancerError("all lite-servers failed to establish connection")
321
+
322
+ async def close(self) -> None:
323
+ task, self._health_task = self._health_task, None
324
+
325
+ if task is not None and not task.done():
326
+ task.cancel()
327
+ with suppress(asyncio.CancelledError):
328
+ await task
329
+
330
+ tasks = [client.close() for client in self._clients]
331
+ await asyncio.gather(*tasks, return_exceptions=True)
332
+
333
+ def _init_clients(
334
+ self,
335
+ clients: t.List[LiteClient],
336
+ ) -> None:
337
+ """
338
+ Validate and register input lite-server clients.
339
+
340
+ Ensures correct client type and network assignment.
341
+ """
342
+ for client in clients:
343
+ if client.TYPE != ClientType.ADNL:
344
+ raise ClientError(
345
+ f"{self.__class__.__name__} can work only with LiteClient instances, "
346
+ f"got {client.__class__.__name__}."
347
+ )
348
+
349
+ client.network = self.network
350
+
351
+ state = LiteClientState(client=client)
352
+ self._clients.append(client)
353
+ self._states.append(state)
354
+
342
355
  def _pick_client(self) -> LiteClient:
343
356
  """
344
357
  Select the best available lite-server client.
@@ -348,10 +361,13 @@ class LiteBalancer(BaseClient):
348
361
  - minimal ping RTT and age among same-height clients
349
362
  - round-robin fallback if no height information
350
363
  """
364
+ if not self.connected:
365
+ raise NotConnectedError(component=self.__class__.__name__)
366
+
351
367
  alive = list(self.alive_clients)
352
368
 
353
369
  if not alive:
354
- raise BalancerError("no alive lite-server clients available")
370
+ raise BalancerError("no alive lite-servers available")
355
371
 
356
372
  height_candidates: t.List[
357
373
  t.Tuple[
@@ -386,7 +402,7 @@ class LiteBalancer(BaseClient):
386
402
 
387
403
  for _ in range(len(self._clients)):
388
404
  candidate = next(self._rr)
389
- if candidate in alive and candidate.is_connected:
405
+ if candidate in alive and candidate.connected:
390
406
  return candidate
391
407
 
392
408
  return alive[0]
@@ -429,6 +445,48 @@ class LiteBalancer(BaseClient):
429
445
  state.retry_after = now + cooldown
430
446
  break
431
447
 
448
+ def _ensure_health_task(self) -> None:
449
+ """
450
+ Ensure background health check task is running.
451
+
452
+ Starts a periodic reconnect loop for unavailable clients
453
+ if it is not already active.
454
+ """
455
+ if self._health_task is not None and not self._health_task.done():
456
+ return
457
+
458
+ loop = asyncio.get_running_loop()
459
+ self._health_task = loop.create_task(
460
+ self._health_loop(),
461
+ name="_health_loop",
462
+ )
463
+
464
+ async def _health_loop(self) -> None:
465
+ """
466
+ Periodically attempt to reconnect dead lite-server clients.
467
+
468
+ Runs until cancelled.
469
+ """
470
+
471
+ async def _recon(c: LiteClient) -> None:
472
+ with suppress(Exception):
473
+ await asyncio.wait_for(
474
+ c.provider.reconnect(),
475
+ timeout=self._connect_timeout,
476
+ )
477
+
478
+ try:
479
+ while True:
480
+ await asyncio.sleep(self._health_interval)
481
+ tasks = [
482
+ _recon(client)
483
+ for client in self.dead_clients
484
+ if not client.connected
485
+ ]
486
+ await asyncio.gather(*tasks, return_exceptions=True)
487
+ except asyncio.CancelledError:
488
+ return
489
+
432
490
  async def _with_failover(
433
491
  self,
434
492
  func: t.Callable[[AdnlProvider], t.Awaitable[_T]],
@@ -452,7 +510,7 @@ class LiteBalancer(BaseClient):
452
510
 
453
511
  client = self._pick_client()
454
512
 
455
- if not client.provider.is_connected:
513
+ if not client.provider.connected:
456
514
  try:
457
515
  await asyncio.wait_for(
458
516
  client.provider.reconnect(),
@@ -482,316 +540,35 @@ class LiteBalancer(BaseClient):
482
540
  return result
483
541
 
484
542
  if last_exc is not None:
485
- raise last_exc
486
-
487
- raise BalancerError("all lite-servers failed to process request")
543
+ raise BalancerError("lite failover exhausted") from last_exc
544
+ raise BalancerError("no alive lite-servers available")
488
545
 
489
546
  try:
490
547
  return await asyncio.wait_for(_run(), timeout=self._request_timeout)
491
548
  except asyncio.TimeoutError as exc:
492
549
  raise ProviderTimeoutError(
493
550
  timeout=self._request_timeout,
494
- endpoint="lite balancer",
551
+ endpoint=self.__class__.__name__,
495
552
  operation="failover request",
496
553
  ) from exc
497
554
 
498
- async def _send_boc(self, boc: str) -> None:
499
- async def _call(provider: AdnlProvider) -> None:
500
- return await provider.send_message(bytes.fromhex(boc))
501
-
502
- return await self._with_failover(_call)
503
-
504
- async def _get_blockchain_config(self) -> t.Dict[int, t.Any]:
505
- async def _call(provider: AdnlProvider) -> t.Dict[int, t.Any]:
506
- return await provider.get_config_all()
507
-
508
- return await self._with_failover(_call)
509
-
510
- async def _get_contract_info(self, address: str) -> ContractStateInfo:
511
- async def _call(provider: AdnlProvider) -> ContractStateInfo:
512
- return await provider.get_account_state(Address(address))
513
-
514
- return await self._with_failover(_call)
515
-
516
- async def _get_transactions(
517
- self,
518
- address: str,
519
- limit: int = 100,
520
- from_lt: t.Optional[int] = None,
521
- to_lt: t.Optional[int] = None,
522
- ) -> t.List[Transaction]:
523
- to_lt = 0 if to_lt is None else to_lt
524
- state = await self._get_contract_info(address)
525
- account = Address(address).to_tl_account_id()
526
-
527
- if state.last_transaction_lt is None or state.last_transaction_hash is None:
528
- return []
529
-
530
- curr_lt = state.last_transaction_lt
531
- curr_hash = state.last_transaction_hash
532
- transactions: t.List[Transaction] = []
533
-
534
- while curr_lt != 0:
535
- fetch_lt = curr_lt
536
- fetch_hash = curr_hash
537
-
538
- async def _call(provider: AdnlProvider) -> t.List[Transaction]:
539
- return await provider.get_transactions(
540
- account=account,
541
- count=16,
542
- from_lt=fetch_lt,
543
- from_hash=fetch_hash,
544
- )
545
-
546
- txs = await self._with_failover(_call)
547
- if not txs:
548
- break
549
-
550
- for tx in txs:
551
- if from_lt is not None and tx.lt > from_lt:
552
- continue
553
- if to_lt > 0 and tx.lt <= to_lt:
554
- return transactions[:limit]
555
-
556
- transactions.append(tx)
557
- if len(transactions) >= limit:
558
- return transactions
559
-
560
- last_tx = txs[-1]
561
- curr_lt = last_tx.prev_trans_lt
562
- curr_hash = last_tx.prev_trans_hash.hex()
563
-
564
- return transactions[:limit]
565
-
566
- async def _run_get_method(
567
- self,
568
- address: str,
569
- method_name: str,
570
- stack: t.Optional[t.List[t.Any]] = None,
571
- ) -> t.List[t.Any]:
572
- async def _call(provider: AdnlProvider) -> t.List[t.Any]:
573
- result = await provider.run_smc_method(
574
- address=Address(address),
575
- method_name=method_name,
576
- stack=encode_stack(stack or []),
577
- )
578
- return decode_stack(result or [])
579
-
580
- return await self._with_failover(_call)
581
-
582
- def _ensure_health_task(self) -> None:
583
- """
584
- Ensure background health check task is running.
585
-
586
- Starts a periodic reconnect loop for unavailable clients
587
- if it is not already active.
588
- """
589
- if self._health_task is not None and not self._health_task.done():
590
- return
591
-
592
- loop = asyncio.get_running_loop()
593
- self._health_task = loop.create_task(
594
- self._health_loop(),
595
- name="_health_loop",
596
- )
597
-
598
- async def _health_loop(self) -> None:
599
- """
600
- Periodically attempt to reconnect dead lite-server clients.
601
-
602
- Runs until cancelled.
603
- """
604
-
605
- async def _recon(c: LiteClient) -> None:
606
- with suppress(Exception):
607
- await asyncio.wait_for(
608
- c.reconnect(),
609
- timeout=self._connect_timeout,
610
- )
611
-
612
- try:
613
- while True:
614
- await asyncio.sleep(self._health_interval)
615
- tasks = [
616
- _recon(client)
617
- for client in self.dead_clients
618
- if not client.is_connected
619
- ]
620
- await asyncio.gather(*tasks, return_exceptions=True)
621
- except asyncio.CancelledError:
622
- return
623
-
624
- async def connect(self) -> None:
625
- if self.is_connected:
626
- self._ensure_health_task()
627
- return
628
-
629
- async def _con(client: LiteClient) -> None:
630
- with suppress(asyncio.TimeoutError):
631
- await asyncio.wait_for(
632
- client.connect(),
633
- timeout=self._connect_timeout,
634
- )
635
-
636
- tasks = [_con(client) for client in self._clients]
637
- await asyncio.gather(*tasks, return_exceptions=True)
638
-
639
- if self.is_connected:
640
- self._ensure_health_task()
641
- return
642
-
643
- raise BalancerError("all lite-servers failed to establish connection")
644
-
645
- async def close(self) -> None:
646
- task, self._health_task = self._health_task, None
647
-
648
- if task is not None and not task.done():
649
- task.cancel()
650
- with suppress(asyncio.CancelledError):
651
- await task
652
-
653
- tasks = [client.close() for client in self._clients]
654
- await asyncio.gather(*tasks, return_exceptions=True)
655
-
656
- async def get_time(self) -> int:
657
- """
658
- Fetch current network time from lite-server.
659
-
660
- :return: Current UNIX timestamp
661
- """
662
-
663
- async def _call(provider: AdnlProvider) -> int:
664
- return await provider.get_time()
665
-
666
- return await self._with_failover(_call)
667
-
668
- async def get_version(self) -> int:
555
+ async def _adnl_call(self, method: str, /, *args: t.Any, **kwargs: t.Any) -> t.Any:
669
556
  """
670
- Fetch lite-server protocol version.
557
+ Execute lite-server call with failover across providers.
671
558
 
672
- :return: Version number
559
+ :param method: Provider coroutine method name.
560
+ :param args: Positional arguments forwarded to the provider method.
561
+ :param kwargs: Keyword arguments forwarded to the provider method.
562
+ :return: Provider method result.
673
563
  """
674
-
675
- async def _call(provider: AdnlProvider) -> int:
676
- return await provider.get_version()
677
-
678
- return await self._with_failover(_call)
679
-
680
- async def wait_masterchain_seqno(
681
- self,
682
- seqno: int,
683
- timeout_ms: int,
684
- schema_name: str,
685
- data: t.Optional[dict] = None,
686
- ) -> dict:
687
- """
688
- Combine waitMasterchainSeqno with another lite-server query.
689
-
690
- :param seqno: Masterchain seqno to wait for
691
- :param timeout_ms: Wait timeout in milliseconds
692
- :param schema_name: Lite-server TL method name without prefix
693
- :param data: Additional method arguments
694
- :return: Lite-server response as dictionary
695
- """
696
-
697
- async def _call(provider: AdnlProvider) -> dict:
698
- return await provider.wait_masterchain_seqno(
699
- seqno=seqno,
700
- timeout_ms=timeout_ms,
701
- schema_name=schema_name,
702
- data=data,
564
+ if not self.connected:
565
+ raise NotConnectedError(
566
+ component=self.__class__.__name__,
567
+ operation=method,
703
568
  )
704
569
 
705
- return await self._with_failover(_call)
706
-
707
- async def get_masterchain_info(self) -> MasterchainInfo:
708
- """
709
- Fetch basic masterchain information.
710
-
711
- :return: MasterchainInfo instance
712
- """
713
-
714
- async def _call(provider: AdnlProvider) -> MasterchainInfo:
715
- return await provider.get_masterchain_info()
716
-
717
- return await self._with_failover(_call)
718
-
719
- async def lookup_block(
720
- self,
721
- workchain: WorkchainID,
722
- shard: int,
723
- seqno: t.Optional[int] = None,
724
- lt: t.Optional[int] = None,
725
- utime: t.Optional[int] = None,
726
- ) -> t.Tuple[BlockIdExt, Block]:
727
- """
728
- Locate a block by workchain/shard and one of seqno/lt/utime.
729
-
730
- :param workchain: Workchain identifier
731
- :param shard: Shard identifier
732
- :param seqno: Block sequence number
733
- :param lt: Logical time filter
734
- :param utime: UNIX time filter
735
- :return: Tuple of BlockIdExt and deserialized Block
736
- """
737
-
738
- async def _call(provider: AdnlProvider) -> t.Tuple[BlockIdExt, Block]:
739
- return await provider.lookup_block(
740
- workchain=workchain,
741
- shard=shard,
742
- seqno=seqno,
743
- lt=lt,
744
- utime=utime,
745
- )
746
-
747
- return await self._with_failover(_call)
748
-
749
- async def get_block_header(
750
- self,
751
- block: BlockIdExt,
752
- ) -> t.Tuple[BlockIdExt, Block]:
753
- """
754
- Fetch and deserialize block header by BlockIdExt.
755
-
756
- :param block: BlockIdExt to query
757
- :return: Tuple of BlockIdExt and deserialized Block
758
- """
759
-
760
- async def _call(provider: AdnlProvider) -> t.Tuple[BlockIdExt, Block]:
761
- return await provider.get_block_header(block)
762
-
763
- return await self._with_failover(_call)
764
-
765
- async def get_block_transactions_ext(
766
- self,
767
- block: BlockIdExt,
768
- count: int = 1024,
769
- ) -> t.List[Transaction]:
770
- """
771
- Fetch extended block transactions list.
772
-
773
- :param block: Target block identifier
774
- :param count: Maximum number of transactions per request
775
- :return: List of deserialized Transaction objects
776
- """
777
-
778
- async def _call(provider: AdnlProvider) -> t.List[Transaction]:
779
- return await provider.get_block_transactions_ext(block, count=count)
780
-
781
- return await self._with_failover(_call)
782
-
783
- async def get_all_shards_info(
784
- self,
785
- block: t.Optional[BlockIdExt] = None,
786
- ) -> t.List[BlockIdExt]:
787
- """
788
- Fetch shard info for all workchains at a given masterchain block.
789
-
790
- :param block: Masterchain block ID or None to use latest
791
- :return: List of shard BlockIdExt objects
792
- """
793
-
794
- async def _call(provider: AdnlProvider) -> t.List[BlockIdExt]:
795
- return await provider.get_all_shards_info(block)
570
+ async def _call(provider: AdnlProvider) -> t.Any:
571
+ fn = getattr(provider, method)
572
+ return await fn(*args, **kwargs)
796
573
 
797
574
  return await self._with_failover(_call)