tonutils 2.0.1b3__py3-none-any.whl → 2.0.1b5__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/__meta__.py +1 -1
- tonutils/cli.py +111 -0
- tonutils/clients/__init__.py +4 -4
- tonutils/clients/adnl/__init__.py +4 -4
- tonutils/clients/adnl/balancer.py +58 -58
- tonutils/clients/adnl/client.py +20 -20
- tonutils/clients/adnl/provider/config.py +13 -8
- tonutils/clients/adnl/provider/provider.py +39 -42
- tonutils/clients/adnl/provider/transport.py +30 -25
- tonutils/clients/base.py +5 -1
- tonutils/exceptions.py +41 -31
- tonutils/tonconnect/__init__.py +0 -0
- tonutils/tools/__init__.py +6 -0
- tonutils/tools/block_scanner/__init__.py +16 -0
- tonutils/tools/block_scanner/annotations.py +23 -0
- tonutils/tools/block_scanner/dispatcher.py +141 -0
- tonutils/tools/block_scanner/events.py +31 -0
- tonutils/tools/block_scanner/scanner.py +313 -0
- tonutils/tools/block_scanner/traversal.py +97 -0
- tonutils/tools/block_scanner/where.py +53 -0
- tonutils/tools/status_monitor/__init__.py +3 -0
- tonutils/tools/status_monitor/console.py +157 -0
- tonutils/tools/status_monitor/models.py +27 -0
- tonutils/tools/status_monitor/monitor.py +295 -0
- tonutils/types.py +12 -4
- {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/METADATA +2 -5
- {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/RECORD +31 -16
- tonutils-2.0.1b5.dist-info/entry_points.txt +2 -0
- {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/WHEEL +0 -0
- {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/licenses/LICENSE +0 -0
- {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import typing as t
|
|
3
|
+
from collections import deque
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from tonutils.tools.status_monitor.models import BlockInfo, LiteServerStatus
|
|
7
|
+
|
|
8
|
+
_ENTER_ALT_SCREEN = "\033[?1049h"
|
|
9
|
+
_EXIT_ALT_SCREEN = "\033[?1049l"
|
|
10
|
+
_HIDE_CURSOR = "\033[?25l"
|
|
11
|
+
_SHOW_CURSOR = "\033[?25h"
|
|
12
|
+
_MOVE_HOME = "\033[H"
|
|
13
|
+
_CLEAR_SCREEN = "\033[2J"
|
|
14
|
+
_CLEAR_LINE = "\033[K"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Console:
|
|
18
|
+
HEADERS = [
|
|
19
|
+
"LS",
|
|
20
|
+
"HOST",
|
|
21
|
+
"PORT",
|
|
22
|
+
"Version",
|
|
23
|
+
"Time",
|
|
24
|
+
"Ping",
|
|
25
|
+
"Connect RTT",
|
|
26
|
+
"Request RTT",
|
|
27
|
+
"Last MC Block",
|
|
28
|
+
"Last BC Block",
|
|
29
|
+
"Archive From",
|
|
30
|
+
]
|
|
31
|
+
WIDTHS = [2, 15, 5, 7, 19, 7, 11, 11, 16, 16, 12]
|
|
32
|
+
|
|
33
|
+
TABLE_TITLE = "Lite Server Status"
|
|
34
|
+
ERROR_TITLE = "Error Log"
|
|
35
|
+
MAX_ERROR_LOGS = 10
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
self._index_width = 2
|
|
39
|
+
self._is_tty = sys.stdout.isatty()
|
|
40
|
+
self._error_log: deque[str] = deque(maxlen=self.MAX_ERROR_LOGS)
|
|
41
|
+
self._prev_errors: t.Dict[int, t.Optional[str]] = {}
|
|
42
|
+
|
|
43
|
+
def enter(self) -> None:
|
|
44
|
+
if self._is_tty:
|
|
45
|
+
sys.stdout.write(_ENTER_ALT_SCREEN + _HIDE_CURSOR + _CLEAR_SCREEN)
|
|
46
|
+
sys.stdout.flush()
|
|
47
|
+
|
|
48
|
+
def exit(self) -> None:
|
|
49
|
+
if self._is_tty:
|
|
50
|
+
sys.stdout.write(_SHOW_CURSOR + _EXIT_ALT_SCREEN)
|
|
51
|
+
sys.stdout.flush()
|
|
52
|
+
|
|
53
|
+
def render(self, statuses: t.List[LiteServerStatus]) -> None:
|
|
54
|
+
self._update_state(statuses)
|
|
55
|
+
self._home()
|
|
56
|
+
self._draw(statuses)
|
|
57
|
+
|
|
58
|
+
def _home(self) -> None:
|
|
59
|
+
if self._is_tty:
|
|
60
|
+
sys.stdout.write(_MOVE_HOME)
|
|
61
|
+
sys.stdout.flush()
|
|
62
|
+
|
|
63
|
+
def _update_state(self, statuses: t.List[LiteServerStatus]) -> None:
|
|
64
|
+
self._update_index_width(statuses)
|
|
65
|
+
self._update_error_log(statuses)
|
|
66
|
+
|
|
67
|
+
def _update_index_width(self, statuses: t.List[LiteServerStatus]) -> None:
|
|
68
|
+
if statuses:
|
|
69
|
+
self._index_width = max(2, len(str(len(statuses) - 1)))
|
|
70
|
+
|
|
71
|
+
def _update_error_log(self, statuses: t.List[LiteServerStatus]) -> None:
|
|
72
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
73
|
+
for status in statuses:
|
|
74
|
+
prev = self._prev_errors.get(status.server.index)
|
|
75
|
+
if status.last_error and status.last_error != prev:
|
|
76
|
+
idx = str(status.server.index).rjust(self._index_width)
|
|
77
|
+
self._error_log.appendleft(f" {now} [LS {idx}]: {status.last_error}")
|
|
78
|
+
self._prev_errors[status.server.index] = status.last_error
|
|
79
|
+
|
|
80
|
+
def _get_table_width(self) -> int:
|
|
81
|
+
return sum(self.WIDTHS) + (len(self.WIDTHS) - 1) * 3
|
|
82
|
+
|
|
83
|
+
def _draw(self, statuses: t.List[LiteServerStatus]) -> None:
|
|
84
|
+
table_width = self._get_table_width()
|
|
85
|
+
padding = (table_width - len(self.TABLE_TITLE)) // 2
|
|
86
|
+
|
|
87
|
+
lines = [
|
|
88
|
+
"═" * table_width,
|
|
89
|
+
" " * padding + self.TABLE_TITLE,
|
|
90
|
+
"═" * table_width,
|
|
91
|
+
self._format_header(),
|
|
92
|
+
self._format_separator(),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
for status in statuses:
|
|
96
|
+
lines.append(self._format_row(status))
|
|
97
|
+
|
|
98
|
+
lines.append("")
|
|
99
|
+
|
|
100
|
+
if self._error_log:
|
|
101
|
+
lines.append("─" * table_width)
|
|
102
|
+
lines.append(f" {self.ERROR_TITLE}:")
|
|
103
|
+
lines.extend(self._error_log)
|
|
104
|
+
|
|
105
|
+
output = (_CLEAR_LINE + "\n").join(lines) + _CLEAR_LINE
|
|
106
|
+
|
|
107
|
+
output += "\n" + _CLEAR_LINE
|
|
108
|
+
sys.stdout.write(output)
|
|
109
|
+
sys.stdout.flush()
|
|
110
|
+
|
|
111
|
+
def _format_header(self) -> str:
|
|
112
|
+
return " │ ".join(h.ljust(w) for h, w in zip(self.HEADERS, self.WIDTHS))
|
|
113
|
+
|
|
114
|
+
def _format_separator(self) -> str:
|
|
115
|
+
return "─┼─".join("─" * w for w in self.WIDTHS)
|
|
116
|
+
|
|
117
|
+
def _format_row(self, status: LiteServerStatus) -> str:
|
|
118
|
+
cells = [
|
|
119
|
+
str(status.server.index),
|
|
120
|
+
status.server.host,
|
|
121
|
+
str(status.server.port),
|
|
122
|
+
self._fmt_int(status.version),
|
|
123
|
+
self._fmt_datetime(status.time),
|
|
124
|
+
self._fmt_ms(status.ping_ms),
|
|
125
|
+
self._fmt_ms(status.connect_ms),
|
|
126
|
+
self._fmt_ms(status.request_ms),
|
|
127
|
+
self._fmt_block(status.last_mc_block),
|
|
128
|
+
self._fmt_block(status.last_bc_block),
|
|
129
|
+
self._fmt_date(status.archive_from),
|
|
130
|
+
]
|
|
131
|
+
return " │ ".join(c.ljust(w) for c, w in zip(cells, self.WIDTHS))
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _fmt_int(value: t.Optional[int]) -> str:
|
|
135
|
+
return str(value) if value is not None else "-"
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _fmt_ms(value: t.Optional[int]) -> str:
|
|
139
|
+
return f"{value}ms" if value is not None else "-"
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _fmt_block(block: t.Optional[BlockInfo]) -> str:
|
|
143
|
+
if block is None:
|
|
144
|
+
return "-"
|
|
145
|
+
return f"{block.seqno} / {block.txs_count}"
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def _fmt_date(ts: t.Optional[int]) -> str:
|
|
149
|
+
if ts is None:
|
|
150
|
+
return "-"
|
|
151
|
+
return datetime.fromtimestamp(ts).strftime("%Y-%m-%d")
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def _fmt_datetime(ts: t.Optional[int]) -> str:
|
|
155
|
+
if ts is None:
|
|
156
|
+
return "-"
|
|
157
|
+
return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LiteServer(BaseModel):
|
|
7
|
+
index: int
|
|
8
|
+
host: str
|
|
9
|
+
port: int
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BlockInfo(BaseModel):
|
|
13
|
+
seqno: int
|
|
14
|
+
txs_count: int
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LiteServerStatus(BaseModel):
|
|
18
|
+
server: LiteServer
|
|
19
|
+
version: t.Optional[int] = None
|
|
20
|
+
time: t.Optional[int] = None
|
|
21
|
+
ping_ms: t.Optional[int] = None
|
|
22
|
+
connect_ms: t.Optional[int] = None
|
|
23
|
+
request_ms: t.Optional[int] = None
|
|
24
|
+
last_mc_block: t.Optional[BlockInfo] = None
|
|
25
|
+
last_bc_block: t.Optional[BlockInfo] = None
|
|
26
|
+
archive_from: t.Optional[int] = None
|
|
27
|
+
last_error: t.Optional[str] = None
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
import typing as t
|
|
6
|
+
|
|
7
|
+
from tonutils.clients import LiteClient
|
|
8
|
+
from tonutils.clients.adnl.provider.models import GlobalConfig
|
|
9
|
+
from tonutils.tools.status_monitor.console import Console
|
|
10
|
+
from tonutils.tools.status_monitor.models import (
|
|
11
|
+
BlockInfo,
|
|
12
|
+
LiteServerStatus,
|
|
13
|
+
LiteServer,
|
|
14
|
+
)
|
|
15
|
+
from tonutils.types import (
|
|
16
|
+
NetworkGlobalID,
|
|
17
|
+
WorkchainID,
|
|
18
|
+
MAINNET_GENESIS_UTIME,
|
|
19
|
+
MASTERCHAIN_SHARD,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LiteServerMonitor:
|
|
24
|
+
RENDER_INTERVAL = 0.1
|
|
25
|
+
RECONNECT_INTERVAL = 30.0
|
|
26
|
+
|
|
27
|
+
FAST_UPDATE_INTERVAL = 0.3
|
|
28
|
+
MEDIUM_UPDATE_INTERVAL = 3.0
|
|
29
|
+
SLOW_UPDATE_INTERVAL = 10.0
|
|
30
|
+
|
|
31
|
+
def __init__(self, clients: t.List[LiteClient]) -> None:
|
|
32
|
+
self._clients = clients
|
|
33
|
+
self._console = Console()
|
|
34
|
+
|
|
35
|
+
self._archive_cache: t.Dict[int, int] = {}
|
|
36
|
+
self._statuses: t.Dict[int, LiteServerStatus] = {}
|
|
37
|
+
self._last_connect: t.Dict[int, float] = {}
|
|
38
|
+
|
|
39
|
+
self._tasks: t.List[asyncio.Task[None]] = []
|
|
40
|
+
self._stop = asyncio.Event()
|
|
41
|
+
|
|
42
|
+
self._locks: t.Dict[int, asyncio.Lock] = {}
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_config(
|
|
46
|
+
cls,
|
|
47
|
+
config: GlobalConfig,
|
|
48
|
+
network: NetworkGlobalID,
|
|
49
|
+
rps_limit: t.Optional[int] = 100,
|
|
50
|
+
) -> LiteServerMonitor:
|
|
51
|
+
return cls(
|
|
52
|
+
[
|
|
53
|
+
LiteClient(
|
|
54
|
+
network=network,
|
|
55
|
+
ip=server.host,
|
|
56
|
+
port=server.port,
|
|
57
|
+
public_key=server.id,
|
|
58
|
+
rps_limit=rps_limit,
|
|
59
|
+
)
|
|
60
|
+
for server in config.liteservers
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def statuses(self) -> t.List[LiteServerStatus]:
|
|
66
|
+
return list(self._statuses.values())
|
|
67
|
+
|
|
68
|
+
async def run(self) -> None:
|
|
69
|
+
self._console.enter()
|
|
70
|
+
self._init_statuses()
|
|
71
|
+
self._start_update_loops()
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
while not self._stop.is_set():
|
|
75
|
+
self._console.render(self.statuses)
|
|
76
|
+
await self._sleep(self.RENDER_INTERVAL)
|
|
77
|
+
finally:
|
|
78
|
+
self._console.exit()
|
|
79
|
+
|
|
80
|
+
async def stop(self) -> None:
|
|
81
|
+
if self._stop.is_set():
|
|
82
|
+
return
|
|
83
|
+
self._stop.set()
|
|
84
|
+
|
|
85
|
+
for task in self._tasks:
|
|
86
|
+
task.cancel()
|
|
87
|
+
await asyncio.gather(*self._tasks, return_exceptions=True)
|
|
88
|
+
|
|
89
|
+
close_tasks = [client.close() for client in self._clients]
|
|
90
|
+
await asyncio.gather(*close_tasks, return_exceptions=True)
|
|
91
|
+
|
|
92
|
+
def _init_statuses(self) -> None:
|
|
93
|
+
for index, client in enumerate(self._clients):
|
|
94
|
+
server = LiteServer(
|
|
95
|
+
index=index,
|
|
96
|
+
host=client.provider.node.host,
|
|
97
|
+
port=client.provider.node.port,
|
|
98
|
+
)
|
|
99
|
+
self._statuses[index] = LiteServerStatus(server=server)
|
|
100
|
+
self._locks[index] = asyncio.Lock()
|
|
101
|
+
|
|
102
|
+
def _start_update_loops(self) -> None:
|
|
103
|
+
if self._tasks:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
for index, client in enumerate(self._clients):
|
|
107
|
+
fast = self._fast_update_loop(index, client)
|
|
108
|
+
self._tasks.append(asyncio.create_task(fast))
|
|
109
|
+
|
|
110
|
+
medium = self._medium_update_loop(index, client)
|
|
111
|
+
self._tasks.append(asyncio.create_task(medium))
|
|
112
|
+
|
|
113
|
+
slow = self._slow_update_loop(index, client)
|
|
114
|
+
self._tasks.append(asyncio.create_task(slow))
|
|
115
|
+
|
|
116
|
+
async def _ensure_connected(self, index: int, client: LiteClient) -> bool:
|
|
117
|
+
if client.provider.is_connected:
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
now = time.monotonic()
|
|
121
|
+
last_attempt = self._last_connect.get(index, 0.0)
|
|
122
|
+
if now - last_attempt < self.RECONNECT_INTERVAL:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
self._last_connect[index] = now
|
|
126
|
+
await self._connect(index, client)
|
|
127
|
+
return client.provider.is_connected
|
|
128
|
+
|
|
129
|
+
async def _fast_update_loop(self, index: int, client: LiteClient) -> None:
|
|
130
|
+
while not self._stop.is_set():
|
|
131
|
+
if not await self._ensure_connected(index, client):
|
|
132
|
+
await self._sleep(1.0)
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
await asyncio.gather(
|
|
136
|
+
self._update_time(index, client),
|
|
137
|
+
self._update_last_blocks(index, client),
|
|
138
|
+
return_exceptions=True,
|
|
139
|
+
)
|
|
140
|
+
await self._sleep(self.FAST_UPDATE_INTERVAL)
|
|
141
|
+
|
|
142
|
+
async def _medium_update_loop(self, index: int, client: LiteClient) -> None:
|
|
143
|
+
while not self._stop.is_set():
|
|
144
|
+
if not client.is_connected:
|
|
145
|
+
await self._sleep(1.0)
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
await asyncio.gather(
|
|
149
|
+
self._update_ping_ms(index, client),
|
|
150
|
+
self._update_request_ms(index, client),
|
|
151
|
+
return_exceptions=True,
|
|
152
|
+
)
|
|
153
|
+
await self._sleep(self.MEDIUM_UPDATE_INTERVAL)
|
|
154
|
+
|
|
155
|
+
async def _slow_update_loop(self, index: int, client: LiteClient) -> None:
|
|
156
|
+
while not self._stop.is_set():
|
|
157
|
+
if not client.is_connected:
|
|
158
|
+
await self._sleep(1.0)
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
await asyncio.gather(
|
|
162
|
+
self._update_version(index, client),
|
|
163
|
+
self._update_archive_from(index, client),
|
|
164
|
+
return_exceptions=True,
|
|
165
|
+
)
|
|
166
|
+
await self._sleep(self.SLOW_UPDATE_INTERVAL)
|
|
167
|
+
|
|
168
|
+
async def _sleep(self, seconds: float) -> None:
|
|
169
|
+
try:
|
|
170
|
+
await asyncio.wait_for(self._stop.wait(), timeout=seconds)
|
|
171
|
+
except asyncio.TimeoutError:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
async def _set_status(self, index: int, **kwargs: t.Any) -> None:
|
|
175
|
+
async with self._locks[index]:
|
|
176
|
+
current = self._statuses[index]
|
|
177
|
+
self._statuses[index] = current.model_copy(update=kwargs)
|
|
178
|
+
|
|
179
|
+
async def _connect(self, index: int, client: LiteClient) -> None:
|
|
180
|
+
try:
|
|
181
|
+
start = time.perf_counter()
|
|
182
|
+
await client.connect()
|
|
183
|
+
connect_ms = int((time.perf_counter() - start) * 1000)
|
|
184
|
+
await self._set_status(index, connect_ms=connect_ms, last_error=None)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
await self._set_status(index, last_error=str(e))
|
|
187
|
+
|
|
188
|
+
async def _update_version(self, index: int, client: LiteClient) -> None:
|
|
189
|
+
try:
|
|
190
|
+
version = await client.get_version()
|
|
191
|
+
await self._set_status(index, version=version)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
await self._set_status(index, last_error=str(e))
|
|
194
|
+
|
|
195
|
+
async def _update_time(self, index: int, client: LiteClient) -> None:
|
|
196
|
+
try:
|
|
197
|
+
server_time = await client.get_time()
|
|
198
|
+
await self._set_status(index, time=server_time)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
await self._set_status(index, last_error=str(e))
|
|
201
|
+
|
|
202
|
+
async def _update_ping_ms(self, index: int, client: LiteClient) -> None:
|
|
203
|
+
try:
|
|
204
|
+
ping_ms = client.provider.last_ping_ms
|
|
205
|
+
if ping_ms is not None:
|
|
206
|
+
await self._set_status(index, ping_ms=ping_ms)
|
|
207
|
+
except Exception as e:
|
|
208
|
+
await self._set_status(index, last_error=str(e))
|
|
209
|
+
|
|
210
|
+
async def _update_request_ms(self, index: int, client: LiteClient) -> None:
|
|
211
|
+
try:
|
|
212
|
+
start = time.perf_counter()
|
|
213
|
+
await client.get_masterchain_info()
|
|
214
|
+
request_ms = int((time.perf_counter() - start) * 1000)
|
|
215
|
+
await self._set_status(index, request_ms=request_ms)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
await self._set_status(index, last_error=str(e))
|
|
218
|
+
|
|
219
|
+
async def _update_last_blocks(self, index: int, client: LiteClient) -> None:
|
|
220
|
+
try:
|
|
221
|
+
mc_block = client.provider.last_mc_block
|
|
222
|
+
if mc_block is None:
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
mc_txs, shards = await asyncio.gather(
|
|
226
|
+
client.get_block_transactions_ext(mc_block),
|
|
227
|
+
client.get_all_shards_info(mc_block),
|
|
228
|
+
)
|
|
229
|
+
last_mc_block = BlockInfo(seqno=mc_block.seqno, txs_count=len(mc_txs))
|
|
230
|
+
|
|
231
|
+
if shards:
|
|
232
|
+
bc_block = max(shards, key=lambda b: b.seqno)
|
|
233
|
+
bc_txs = await client.get_block_transactions_ext(bc_block)
|
|
234
|
+
last_bc_block = BlockInfo(seqno=bc_block.seqno, txs_count=len(bc_txs))
|
|
235
|
+
await self._set_status(
|
|
236
|
+
index,
|
|
237
|
+
last_mc_block=last_mc_block,
|
|
238
|
+
last_bc_block=last_bc_block,
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
await self._set_status(index, last_mc_block=last_mc_block)
|
|
242
|
+
|
|
243
|
+
except Exception as e:
|
|
244
|
+
await self._set_status(index, last_error=str(e))
|
|
245
|
+
|
|
246
|
+
async def _update_archive_from(self, index: int, client: LiteClient) -> None:
|
|
247
|
+
try:
|
|
248
|
+
now = int(time.time())
|
|
249
|
+
result = await self._find_archive_depth(
|
|
250
|
+
client, now, self._archive_cache.get(index)
|
|
251
|
+
)
|
|
252
|
+
self._archive_cache[index] = result
|
|
253
|
+
await self._set_status(index, archive_from=result)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
await self._set_status(index, last_error=str(e))
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
async def _find_archive_depth(
|
|
259
|
+
client: LiteClient,
|
|
260
|
+
now: int,
|
|
261
|
+
cached: t.Optional[int] = None,
|
|
262
|
+
) -> int:
|
|
263
|
+
seconds_per_day = 86400
|
|
264
|
+
seconds_diff = now - MAINNET_GENESIS_UTIME
|
|
265
|
+
right = seconds_diff // seconds_per_day
|
|
266
|
+
|
|
267
|
+
if cached is not None:
|
|
268
|
+
cached_days = (now - cached) // seconds_per_day
|
|
269
|
+
left = cached_days
|
|
270
|
+
best_days = cached_days
|
|
271
|
+
else:
|
|
272
|
+
left = 0
|
|
273
|
+
best_days = 0
|
|
274
|
+
|
|
275
|
+
async def probe(days: int) -> bool:
|
|
276
|
+
utime = now - days * seconds_per_day
|
|
277
|
+
try:
|
|
278
|
+
await client.provider.lookup_block(
|
|
279
|
+
workchain=WorkchainID.MASTERCHAIN,
|
|
280
|
+
shard=MASTERCHAIN_SHARD,
|
|
281
|
+
utime=utime,
|
|
282
|
+
)
|
|
283
|
+
return True
|
|
284
|
+
except (Exception,):
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
while left <= right:
|
|
288
|
+
mid = (left + right) // 2
|
|
289
|
+
if await probe(mid):
|
|
290
|
+
best_days = mid
|
|
291
|
+
left = mid + 1
|
|
292
|
+
else:
|
|
293
|
+
right = mid - 1
|
|
294
|
+
|
|
295
|
+
return now - best_days * seconds_per_day
|
tonutils/types.py
CHANGED
|
@@ -17,10 +17,6 @@ __all__ = [
|
|
|
17
17
|
"ClientType",
|
|
18
18
|
"ContractState",
|
|
19
19
|
"ContractStateInfo",
|
|
20
|
-
"DEFAULT_ADNL_RETRY_POLICY",
|
|
21
|
-
"DEFAULT_HTTP_RETRY_POLICY",
|
|
22
|
-
"DEFAULT_SENDMODE",
|
|
23
|
-
"DEFAULT_SUBWALLET_ID",
|
|
24
20
|
"DNSCategory",
|
|
25
21
|
"DNSPrefix",
|
|
26
22
|
"MetadataPrefix",
|
|
@@ -34,6 +30,12 @@ __all__ = [
|
|
|
34
30
|
"StackItems",
|
|
35
31
|
"StackTag",
|
|
36
32
|
"WorkchainID",
|
|
33
|
+
"DEFAULT_ADNL_RETRY_POLICY",
|
|
34
|
+
"DEFAULT_HTTP_RETRY_POLICY",
|
|
35
|
+
"DEFAULT_SENDMODE",
|
|
36
|
+
"DEFAULT_SUBWALLET_ID",
|
|
37
|
+
"MAINNET_GENESIS_UTIME",
|
|
38
|
+
"MASTERCHAIN_SHARD",
|
|
37
39
|
]
|
|
38
40
|
|
|
39
41
|
from tonutils.exceptions import CDN_CHALLENGE_MARKERS
|
|
@@ -495,3 +497,9 @@ DEFAULT_SUBWALLET_ID = 698983191
|
|
|
495
497
|
|
|
496
498
|
DEFAULT_SENDMODE = SendMode.PAY_GAS_SEPARATELY | SendMode.IGNORE_ERRORS
|
|
497
499
|
"""Default send mode: pay fees separately and ignore errors."""
|
|
500
|
+
|
|
501
|
+
MASTERCHAIN_SHARD = -9223372036854775808
|
|
502
|
+
"""Shard identifier for the masterchain (-2^63)."""
|
|
503
|
+
|
|
504
|
+
MAINNET_GENESIS_UTIME = 1573822385
|
|
505
|
+
"""Unix timestamp of the TON mainnet genesis block (November 15, 2019)."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tonutils
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1b5
|
|
4
4
|
Summary: Tonutils is a high-level, object-oriented Python library designed to facilitate seamless interactions with the TON blockchain.
|
|
5
5
|
Author: nessshon
|
|
6
6
|
Maintainer: nessshon
|
|
@@ -26,11 +26,8 @@ Requires-Python: <3.15,>=3.10
|
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Dist: aiohttp>=3.7.0
|
|
29
|
-
Requires-Dist: pycryptodomex~=3.23.0
|
|
30
29
|
Requires-Dist: pydantic<3.0,>=2.0
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist: pytoniq-core~=0.1.45
|
|
33
|
-
Requires-Dist: requests>=2.31.0
|
|
30
|
+
Requires-Dist: pytoniq-core~=0.1.46
|
|
34
31
|
Dynamic: license-file
|
|
35
32
|
|
|
36
33
|
# 📦 Tonutils 2.0 [BETA]
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
tonutils/__init__.py,sha256=ueJrDkU1JBlZiX0q8roQfzYOZY62Of_CiHZlxIIQFO0,228
|
|
2
|
-
tonutils/__meta__.py,sha256=
|
|
3
|
-
tonutils/
|
|
2
|
+
tonutils/__meta__.py,sha256=uHnZdT43kWVuqeXaVY86NLU06cIBG4SuxsLdEUaX6v8,24
|
|
3
|
+
tonutils/cli.py,sha256=WGir-ihgPuKTgKGmhjPZeKk9wgsm64jiJciOnVlsdco,2645
|
|
4
|
+
tonutils/exceptions.py,sha256=67jXCFPyOnVnd8EaqYg1osVIXg34VZX-aJHPunpc-oI,6462
|
|
4
5
|
tonutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
tonutils/types.py,sha256=
|
|
6
|
+
tonutils/types.py,sha256=ORevKllLjb7pmL3AQTMeSoiZQ4KFhoB_rA7KpGqZZjU,14542
|
|
6
7
|
tonutils/utils.py,sha256=w2sXvVMA2Erz_JSSN2WuYSi7RAPDAOOwWYiThu8Q0Ps,16499
|
|
7
|
-
tonutils/clients/__init__.py,sha256=
|
|
8
|
-
tonutils/clients/base.py,sha256=
|
|
8
|
+
tonutils/clients/__init__.py,sha256=PvHsOHOtkVonfiowEPi6P7ggDeQeQorXcksujru6efQ,561
|
|
9
|
+
tonutils/clients/base.py,sha256=DjNhFd_p82Osqn0qWIjflpt9S1NVZc3MQIEj5U5E9-k,9631
|
|
9
10
|
tonutils/clients/limiter.py,sha256=d2WO7Dx-ksg3gvl05WuDMu1KycuQj0XrmiG67Q7wnFo,3592
|
|
10
11
|
tonutils/clients/protocol.py,sha256=4QerYoqBKAAfNZVpyQW2sp5KZZlq8xHCn9yWbbJERPQ,2557
|
|
11
|
-
tonutils/clients/adnl/__init__.py,sha256=
|
|
12
|
-
tonutils/clients/adnl/balancer.py,sha256=
|
|
13
|
-
tonutils/clients/adnl/client.py,sha256=
|
|
12
|
+
tonutils/clients/adnl/__init__.py,sha256=shkczPIYlTrOHjpnOBIoRwEYRQ4rVGfLnOZvnQY0a5U,174
|
|
13
|
+
tonutils/clients/adnl/balancer.py,sha256=L5tvSwklgsp1QGBypMcxta_vhGdCXrOahi33-EFMPfs,26877
|
|
14
|
+
tonutils/clients/adnl/client.py,sha256=hClnodq57bi6Q2p-9vbmh89GKpRDwXdIABUM8BEtKUs,14111
|
|
14
15
|
tonutils/clients/adnl/utils.py,sha256=rKLbTiGbZQ0ozvh5jYjjaFEKsmqEschjuUiHOX9e6ps,5154
|
|
15
16
|
tonutils/clients/adnl/provider/__init__.py,sha256=lLwFEEgCifMlYozwSzQbvq97R6Du7TzgGFnTgUnk4gw,63
|
|
16
|
-
tonutils/clients/adnl/provider/config.py,sha256=
|
|
17
|
+
tonutils/clients/adnl/provider/config.py,sha256=nhe9jwDeN6d4yTlgaVHGKk5TaALYSvIfaok2myvIJXY,894
|
|
17
18
|
tonutils/clients/adnl/provider/models.py,sha256=3dn4oZv7PIgiwlP2lGs0O6VckC19ejxCFlSPhzU0wX8,4537
|
|
18
|
-
tonutils/clients/adnl/provider/provider.py,sha256=
|
|
19
|
-
tonutils/clients/adnl/provider/transport.py,sha256=
|
|
19
|
+
tonutils/clients/adnl/provider/provider.py,sha256=Er5RNR490RIQlRTI5KMxVarshHRNVseAbUYGM48fcbg,24180
|
|
20
|
+
tonutils/clients/adnl/provider/transport.py,sha256=lgp1N0LGjYYIKWiwBIm-pnF064TkkCsSTDAFKt8_U-I,10983
|
|
20
21
|
tonutils/clients/adnl/provider/workers/__init__.py,sha256=M65q7mVfinHImIZNCEaHBJ-SO4DdVBsSkeZFrJs9OoE,177
|
|
21
22
|
tonutils/clients/adnl/provider/workers/base.py,sha256=8RenR2MU_B-b8mevzCytLJmPgjLCJ8bVI-4q-wx78Q8,2031
|
|
22
23
|
tonutils/clients/adnl/provider/workers/pinger.py,sha256=lBpy4TU8L-I_6sxEft-Bn9XmntJhMg5Rm2o2tN2Mp0A,2443
|
|
@@ -86,8 +87,22 @@ tonutils/contracts/wallet/versions/v2.py,sha256=pwrlan-utZo_WmnzDwSbnzV8ibkPEWx2
|
|
|
86
87
|
tonutils/contracts/wallet/versions/v3.py,sha256=d7cM8wjmW-1H7jGuY3AuUd7eTY3wq9ZYpJ4f5OeYX08,2470
|
|
87
88
|
tonutils/contracts/wallet/versions/v4.py,sha256=2sAsjJ8_3oYAj5JwWH3PiMyoGbgl6-f7-p6T5X7MGTI,2713
|
|
88
89
|
tonutils/contracts/wallet/versions/v5.py,sha256=1J6KXPOc7Q5S5EdFM9WXQzNGRZrw5EgxDZ9dmyHwsXE,8890
|
|
89
|
-
tonutils
|
|
90
|
-
tonutils
|
|
91
|
-
tonutils
|
|
92
|
-
tonutils
|
|
93
|
-
tonutils
|
|
90
|
+
tonutils/tonconnect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
|
+
tonutils/tools/__init__.py,sha256=QYOVuGY50FFkWlgIvHc2RPU3xiEWSbwnwZ6wuZPQnCA,102
|
|
92
|
+
tonutils/tools/block_scanner/__init__.py,sha256=NRZ2XuDL3VwXJJ0iMe6ETh5w1aX4pCMMIv9UmmOj5yE,260
|
|
93
|
+
tonutils/tools/block_scanner/annotations.py,sha256=i6Xsu_rBERr7qEmhGGcwlYYkqf8cLjNJlZ4rXUf51WY,795
|
|
94
|
+
tonutils/tools/block_scanner/dispatcher.py,sha256=lgrs0MPnnyx0VibVQGYXU6s5mpqV_lO47yhO0uOCV30,4393
|
|
95
|
+
tonutils/tools/block_scanner/events.py,sha256=6M80JoiVMRy7kBBuT8-KlHJ5Dx3GHUjIKmZVPaXXeaA,727
|
|
96
|
+
tonutils/tools/block_scanner/scanner.py,sha256=2arPhNPu-6Klcj-PnSnP3DBVWekhlYjn2iqvN-1wigU,9929
|
|
97
|
+
tonutils/tools/block_scanner/traversal.py,sha256=Zp0Uon0OTvLTsZi-zviMyvJHBsZRUx9XExxLU6LMkVA,3101
|
|
98
|
+
tonutils/tools/block_scanner/where.py,sha256=sZUuabQ5joATkJqUh5R3N5r4P9WblmRrVYEVdZeJvZw,1239
|
|
99
|
+
tonutils/tools/status_monitor/__init__.py,sha256=QnMlA0IDLtCGgXsEgB9q3EJTBo2s5js6lSJk0oZkQZQ,72
|
|
100
|
+
tonutils/tools/status_monitor/console.py,sha256=UX3BzjjzeS_nKFGg4NkZJpu9fR_IAJZdQUMz0HcJCdg,5036
|
|
101
|
+
tonutils/tools/status_monitor/models.py,sha256=yHuiEuij4h2kVoOK3sbhNq6SwiGDW_evZmzUwMy1GQs,608
|
|
102
|
+
tonutils/tools/status_monitor/monitor.py,sha256=8zUwNwFScmcjK9ES7XX1LZWjw49lk8CSUQATcUYM57E,10085
|
|
103
|
+
tonutils-2.0.1b5.dist-info/licenses/LICENSE,sha256=fG-yM-8DSkOTaJ558P7uF5PNXBmineVO9-HC12YbIxs,1060
|
|
104
|
+
tonutils-2.0.1b5.dist-info/METADATA,sha256=1Wvi3Vlsb0IYwLaKlchY-OdFZ_TYNmHmZIzuyDQj55w,4181
|
|
105
|
+
tonutils-2.0.1b5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
106
|
+
tonutils-2.0.1b5.dist-info/entry_points.txt,sha256=qijo1cqvbbzLVbXp-PCYh19Pgmd7duH6yljmnUPd55I,47
|
|
107
|
+
tonutils-2.0.1b5.dist-info/top_level.txt,sha256=-7H_mGl8S9HKQrkUiTLmEbtMM-knzRzd_a0cZZnuZIU,9
|
|
108
|
+
tonutils-2.0.1b5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|