tonutils 2.0.1b2__py3-none-any.whl → 2.0.1b4__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/cli.py +111 -0
- tonutils/clients/__init__.py +7 -11
- tonutils/clients/adnl/__init__.py +7 -3
- tonutils/clients/adnl/balancer.py +362 -168
- tonutils/clients/adnl/client.py +203 -67
- tonutils/clients/adnl/provider/config.py +24 -25
- tonutils/clients/adnl/provider/models.py +4 -0
- tonutils/clients/adnl/provider/provider.py +203 -160
- tonutils/clients/adnl/provider/transport.py +44 -33
- 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 +4 -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/vanity.py +1 -1
- 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 +146 -228
- tonutils/tonconnect/__init__.py +0 -0
- tonutils/tools/__init__.py +6 -0
- tonutils/tools/block_scanner/__init__.py +26 -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 +315 -0
- tonutils/tools/block_scanner/traversal.py +96 -0
- tonutils/tools/block_scanner/where.py +151 -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 +125 -2
- tonutils/utils.py +3 -3
- {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b4.dist-info}/METADATA +2 -5
- tonutils-2.0.1b4.dist-info/RECORD +108 -0
- tonutils-2.0.1b4.dist-info/entry_points.txt +2 -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.1b2.dist-info/RECORD +0 -98
- /tonutils/{protocols/client.py → clients/protocol.py} +0 -0
- {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b4.dist-info}/WHEEL +0 -0
- {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b4.dist-info}/licenses/LICENSE +0 -0
- {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b4.dist-info}/top_level.txt +0 -0
tonutils/exceptions.py
CHANGED
|
@@ -1,294 +1,212 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import typing as t
|
|
2
3
|
|
|
3
|
-
from pytoniq_core import Address
|
|
4
|
-
|
|
5
4
|
__all__ = [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"ContractError",
|
|
5
|
+
"TonutilsError",
|
|
6
|
+
"TransportError",
|
|
7
|
+
"ProviderError",
|
|
10
8
|
"ClientError",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"AdnlBalancerConnectionError",
|
|
18
|
-
"RateLimitExceededError",
|
|
9
|
+
"ContractError",
|
|
10
|
+
"BalancerError",
|
|
11
|
+
"NotConnectedError",
|
|
12
|
+
"ProviderTimeoutError",
|
|
13
|
+
"ProviderResponseError",
|
|
14
|
+
"RetryLimitError",
|
|
19
15
|
"RunGetMethodError",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"AdnlTransportStateError",
|
|
23
|
-
"AdnlTransportCipherError",
|
|
24
|
-
"AdnlTransportFrameError",
|
|
16
|
+
"StateNotLoadedError",
|
|
17
|
+
"CDN_CHALLENGE_MARKERS",
|
|
25
18
|
]
|
|
26
19
|
|
|
27
20
|
|
|
28
|
-
class
|
|
29
|
-
"""Base exception for
|
|
21
|
+
class TonutilsError(Exception):
|
|
22
|
+
"""Base exception for tonutils."""
|
|
30
23
|
|
|
31
|
-
@classmethod
|
|
32
|
-
def _obj_name(cls, obj: t.Union[object, type, str]) -> str:
|
|
33
|
-
"""
|
|
34
|
-
Resolve a human-readable class name from an object or type.
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"""
|
|
39
|
-
if isinstance(obj, type):
|
|
40
|
-
return obj.__name__
|
|
41
|
-
if isinstance(obj, str):
|
|
42
|
-
return obj
|
|
43
|
-
return obj.__class__.__name__
|
|
25
|
+
class TransportError(TonutilsError):
|
|
26
|
+
"""Transport-level failure with structured context.
|
|
44
27
|
|
|
28
|
+
Covers: TCP connect, ADNL handshake, send/recv, crypto failures.
|
|
45
29
|
|
|
46
|
-
|
|
30
|
+
:param endpoint: Server address as "host:port"
|
|
31
|
+
:param operation: What was attempted ("connect", "handshake", "send", "recv")
|
|
32
|
+
:param reason: Why it failed ("timeout 2.0s", "connection refused", etc.)
|
|
47
33
|
"""
|
|
48
|
-
Raised when a client method is called without an active connection.
|
|
49
34
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
endpoint: str
|
|
36
|
+
operation: str
|
|
37
|
+
reason: str
|
|
53
38
|
|
|
54
|
-
def __init__(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
endpoint: str,
|
|
43
|
+
operation: str,
|
|
44
|
+
reason: str,
|
|
45
|
+
) -> None:
|
|
46
|
+
self.endpoint = endpoint
|
|
47
|
+
self.operation = operation
|
|
48
|
+
self.reason = reason
|
|
49
|
+
super().__init__(f"{operation} failed at {endpoint}: {reason}")
|
|
61
50
|
|
|
62
51
|
|
|
63
|
-
class
|
|
64
|
-
"""
|
|
65
|
-
Raised when accessing derived state before an explicit refresh.
|
|
52
|
+
class ProviderError(TonutilsError):
|
|
53
|
+
"""Raise on provider-level failures (protocol, parsing, session/state)."""
|
|
66
54
|
|
|
67
|
-
Typical usage is for contract wrappers that require refresh()
|
|
68
|
-
to be called before accessing state_info or derived properties.
|
|
69
|
-
"""
|
|
70
55
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
super().__init__(
|
|
74
|
-
f"Access to `{attr}` is not allowed.\n"
|
|
75
|
-
f"Call `await {name}.refresh()` before accessing `{attr}`."
|
|
76
|
-
)
|
|
56
|
+
class ClientError(TonutilsError):
|
|
57
|
+
"""Raise on client misuse, validation errors, or unsupported operations."""
|
|
77
58
|
|
|
78
59
|
|
|
79
|
-
class
|
|
80
|
-
"""
|
|
81
|
-
Generic error related to smart contract helpers.
|
|
60
|
+
class BalancerError(TonutilsError):
|
|
61
|
+
"""Raise on balancer failures (no alive backends, failover exhausted)."""
|
|
82
62
|
|
|
83
|
-
Used for configuration issues, invalid versions and similar
|
|
84
|
-
contract wrapper problems.
|
|
85
|
-
"""
|
|
86
63
|
|
|
87
|
-
|
|
88
|
-
|
|
64
|
+
class NotConnectedError(TonutilsError, RuntimeError):
|
|
65
|
+
"""Raise when an operation requires an active connection."""
|
|
89
66
|
|
|
67
|
+
endpoint: t.Optional[str]
|
|
90
68
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
69
|
+
def __init__(self, endpoint: t.Optional[str] = None) -> None:
|
|
70
|
+
self.endpoint = endpoint
|
|
71
|
+
if endpoint:
|
|
72
|
+
super().__init__(f"not connected to {endpoint}")
|
|
73
|
+
else:
|
|
74
|
+
super().__init__("not connected")
|
|
94
75
|
|
|
95
|
-
Used for issues related to specific client implementations
|
|
96
|
-
(HTTP, ADNL, balancers, etc.).
|
|
97
|
-
"""
|
|
98
76
|
|
|
77
|
+
class ProviderTimeoutError(ProviderError, asyncio.TimeoutError):
|
|
78
|
+
"""Raise when a provider operation exceeds its timeout.
|
|
99
79
|
|
|
100
|
-
|
|
80
|
+
:param timeout: Timeout in seconds.
|
|
81
|
+
:param endpoint: Endpoint identifier (URL or host:port).
|
|
82
|
+
:param operation: Operation label (e.g. "request", "connect").
|
|
101
83
|
"""
|
|
102
|
-
Lite-server reported an internal error while processing a request.
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
|
|
85
|
+
timeout: float
|
|
86
|
+
endpoint: str
|
|
87
|
+
operation: str
|
|
106
88
|
|
|
107
|
-
def __init__(self,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
""
|
|
112
|
-
super().__init__(f"Lite-server crashed with `{code}` code. Message: {message}.")
|
|
113
|
-
self.message = message
|
|
114
|
-
self.code = code
|
|
89
|
+
def __init__(self, *, timeout: float, endpoint: str, operation: str) -> None:
|
|
90
|
+
self.timeout = timeout
|
|
91
|
+
self.endpoint = endpoint
|
|
92
|
+
self.operation = operation
|
|
93
|
+
super().__init__(f"{operation} timed out after {timeout}s at {endpoint}")
|
|
115
94
|
|
|
116
95
|
|
|
117
|
-
class
|
|
118
|
-
"""
|
|
119
|
-
Base error for ADNL provider failures.
|
|
96
|
+
class ProviderResponseError(ProviderError):
|
|
97
|
+
"""Raise when a backend returns an error response.
|
|
120
98
|
|
|
121
|
-
|
|
99
|
+
:param code: Backend code (HTTP status or lite-server code).
|
|
100
|
+
:param message: Backend error description.
|
|
101
|
+
:param endpoint: Endpoint identifier (URL or host:port).
|
|
122
102
|
"""
|
|
123
103
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
:param host: Lite-server host
|
|
128
|
-
:param port: Lite-server port
|
|
129
|
-
"""
|
|
130
|
-
full_message = f"{message} ({host}:{port})."
|
|
131
|
-
super().__init__(full_message)
|
|
132
|
-
self.host = host
|
|
133
|
-
self.port = port
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
class AdnlProviderConnectError(AdnlProviderError):
|
|
137
|
-
"""
|
|
138
|
-
Failed to establish an ADNL connection to the lite-server.
|
|
104
|
+
code: int
|
|
105
|
+
message: str
|
|
106
|
+
endpoint: str
|
|
139
107
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
super().__init__(
|
|
145
|
-
f"Failed to connect: {message}.",
|
|
146
|
-
host=host,
|
|
147
|
-
port=port,
|
|
148
|
-
)
|
|
108
|
+
def __init__(self, *, code: int, message: str, endpoint: str) -> None:
|
|
109
|
+
self.code = code
|
|
110
|
+
self.message = message
|
|
111
|
+
self.endpoint = endpoint
|
|
112
|
+
super().__init__(f"request failed with code {code} at {endpoint}: {message}")
|
|
149
113
|
|
|
150
114
|
|
|
151
|
-
class
|
|
152
|
-
"""
|
|
153
|
-
ADNL provider was closed while waiting for a response.
|
|
115
|
+
class RetryLimitError(ProviderError):
|
|
116
|
+
"""Raise when retry policy is exhausted for a matched rule.
|
|
154
117
|
|
|
155
|
-
|
|
156
|
-
|
|
118
|
+
:param attempts: Attempts already performed for the matched rule.
|
|
119
|
+
:param max_attempts: Maximum attempts allowed by the matched rule.
|
|
120
|
+
:param last_error: Last provider error that triggered a retry.
|
|
157
121
|
"""
|
|
158
122
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
host=host,
|
|
163
|
-
port=port,
|
|
164
|
-
)
|
|
165
|
-
|
|
123
|
+
attempts: int
|
|
124
|
+
max_attempts: int
|
|
125
|
+
last_error: ProviderError
|
|
166
126
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
*,
|
|
130
|
+
attempts: int,
|
|
131
|
+
max_attempts: int,
|
|
132
|
+
last_error: ProviderError,
|
|
133
|
+
) -> None:
|
|
134
|
+
self.attempts = attempts
|
|
135
|
+
self.max_attempts = max_attempts
|
|
136
|
+
self.last_error = last_error
|
|
137
|
+
super().__init__(f"retry exhausted ({attempts}/{max_attempts}): {last_error}")
|
|
174
138
|
|
|
175
|
-
def __init__(self, host: str, port: int) -> None:
|
|
176
|
-
super().__init__(
|
|
177
|
-
"Invalid response from provider.",
|
|
178
|
-
host=host,
|
|
179
|
-
port=port,
|
|
180
|
-
)
|
|
181
139
|
|
|
140
|
+
class ContractError(ClientError):
|
|
141
|
+
"""Raise when a contract wrapper operation fails.
|
|
182
142
|
|
|
183
|
-
class
|
|
143
|
+
:param target: Contract instance or contract class related to the failure.
|
|
144
|
+
:param message: Human-readable error message.
|
|
184
145
|
"""
|
|
185
|
-
Lite-server reported that a requested block is missing.
|
|
186
146
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"""
|
|
147
|
+
target: t.Any
|
|
148
|
+
message: str
|
|
190
149
|
|
|
191
|
-
def __init__(self,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
150
|
+
def __init__(self, target: t.Any, message: str) -> None:
|
|
151
|
+
self.target = target
|
|
152
|
+
self.message = message
|
|
153
|
+
name = (
|
|
154
|
+
target.__name__ if isinstance(target, type) else target.__class__.__name__
|
|
196
155
|
)
|
|
197
|
-
|
|
156
|
+
super().__init__(f"{name}: {message}")
|
|
198
157
|
|
|
199
158
|
|
|
200
|
-
class
|
|
201
|
-
"""
|
|
202
|
-
All ADNL lite-server providers failed to connect or process a request.
|
|
159
|
+
class StateNotLoadedError(ContractError):
|
|
160
|
+
"""Raise when a contract wrapper requires state that is not loaded.
|
|
203
161
|
|
|
204
|
-
|
|
162
|
+
:param contract: Contract instance related to the failure.
|
|
163
|
+
:param missing: Missing field name (e.g. "state_info", "state_data").
|
|
205
164
|
"""
|
|
206
165
|
|
|
166
|
+
missing: str
|
|
207
167
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
Raised after exhausting configured retry attempts.
|
|
213
|
-
"""
|
|
214
|
-
|
|
215
|
-
def __init__(self, attempts: int) -> None:
|
|
216
|
-
"""
|
|
217
|
-
:param attempts: Number of attempts performed before giving up
|
|
218
|
-
"""
|
|
219
|
-
super().__init__(f"Rate limit exceeded after `{attempts}` attempts.")
|
|
220
|
-
self.attempts = attempts
|
|
168
|
+
def __init__(self, contract: t.Any, *, missing: str) -> None:
|
|
169
|
+
self.missing = missing
|
|
170
|
+
name = contract.__class__.__name__
|
|
171
|
+
super().__init__(contract, f"{missing} is not loaded. Call {name}.refresh().")
|
|
221
172
|
|
|
222
173
|
|
|
223
174
|
class RunGetMethodError(ClientError):
|
|
224
|
-
"""
|
|
225
|
-
get-method execution failed with a non-zero exit code.
|
|
175
|
+
"""Raise when a contract get-method returns a non-zero TVM exit code.
|
|
226
176
|
|
|
227
|
-
|
|
177
|
+
:param address: Contract address (string form).
|
|
178
|
+
:param method_name: Get-method name.
|
|
179
|
+
:param exit_code: TVM exit code.
|
|
228
180
|
"""
|
|
229
181
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
super().__init__(
|
|
237
|
-
f"Get method `{method_name}` on `{address.to_str()}` "
|
|
238
|
-
f"failed with `{exit_code}` exit code."
|
|
239
|
-
)
|
|
182
|
+
address: str
|
|
183
|
+
method_name: str
|
|
184
|
+
exit_code: int
|
|
185
|
+
|
|
186
|
+
def __init__(self, *, address: str, method_name: str, exit_code: int) -> None:
|
|
187
|
+
self.address = address
|
|
240
188
|
self.method_name = method_name
|
|
241
189
|
self.exit_code = exit_code
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
class AdnlTransportError(TonutilsException):
|
|
246
|
-
"""
|
|
247
|
-
Base error for raw ADNL transport failures.
|
|
248
|
-
|
|
249
|
-
Covers handshake, cipher initialization, framing and state issues.
|
|
250
|
-
"""
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
class AdnlHandshakeError(AdnlTransportError):
|
|
254
|
-
"""
|
|
255
|
-
ADNL handshake failed during initial connection.
|
|
256
|
-
|
|
257
|
-
Raised when the remote side closes the connection or does not
|
|
258
|
-
respond within the expected timeout.
|
|
259
|
-
"""
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
class AdnlTransportStateError(AdnlTransportError):
|
|
263
|
-
"""
|
|
264
|
-
Invalid internal state of the ADNL transport.
|
|
265
|
-
|
|
266
|
-
Raised when required transport components (reader, writer, cipher, etc.)
|
|
267
|
-
are not initialized or used incorrectly.
|
|
268
|
-
"""
|
|
269
|
-
|
|
270
|
-
def __init__(self, message: str) -> None:
|
|
271
|
-
super().__init__(f"ADNL transport state error: {message}.")
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
class AdnlTransportCipherError(AdnlTransportError):
|
|
275
|
-
"""
|
|
276
|
-
ADNL cipher was used before being initialized.
|
|
277
|
-
|
|
278
|
-
Raised when trying to encrypt or decrypt frames without
|
|
279
|
-
a valid session cipher.
|
|
280
|
-
"""
|
|
281
|
-
|
|
282
|
-
def __init__(self, direction: str) -> None:
|
|
283
|
-
super().__init__(f"ADNL {direction} cipher is not initialized.")
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
class AdnlTransportFrameError(AdnlTransportError):
|
|
287
|
-
"""
|
|
288
|
-
Malformed or invalid ADNL frame was received.
|
|
190
|
+
super().__init__(
|
|
191
|
+
f"get-method '{method_name}' failed for {address} with exit code {exit_code}"
|
|
192
|
+
)
|
|
289
193
|
|
|
290
|
-
Raised when frame length, structure or checksum validation fails.
|
|
291
|
-
"""
|
|
292
194
|
|
|
293
|
-
|
|
294
|
-
|
|
195
|
+
CDN_CHALLENGE_MARKERS: t.Dict[str, str] = {
|
|
196
|
+
# Cloudflare
|
|
197
|
+
"cloudflare": "Cloudflare protection triggered or blocked the request.",
|
|
198
|
+
"cf-ray": "Cloudflare intermediate error (cf-ray header detected).",
|
|
199
|
+
"just a moment": "Cloudflare browser verification page.",
|
|
200
|
+
"checking your browser": "Cloudflare browser verification page.",
|
|
201
|
+
"attention required": "Cloudflare challenge page.",
|
|
202
|
+
"captcha": "Cloudflare CAPTCHA challenge.",
|
|
203
|
+
# Other CDNs / proxies
|
|
204
|
+
"akamai": "Akamai CDN blocked or intercepted the request.",
|
|
205
|
+
"fastly": "Fastly CDN error response detected.",
|
|
206
|
+
"varnish": "Varnish cache/CDN interference.",
|
|
207
|
+
"nginx": "Reverse proxy (nginx) error response.",
|
|
208
|
+
# Upstream failures
|
|
209
|
+
"502 bad gateway": "Bad gateway from upstream or proxy.",
|
|
210
|
+
"503 service unavailable": "Service temporarily unavailable (proxy or CDN).",
|
|
211
|
+
"ddos": "Possible DDoS protection or mitigation page.",
|
|
212
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .events import (
|
|
2
|
+
BlockEvent,
|
|
3
|
+
TransactionEvent,
|
|
4
|
+
TransactionsEvent,
|
|
5
|
+
)
|
|
6
|
+
from .scanner import BlockScanner
|
|
7
|
+
from .where import (
|
|
8
|
+
Where,
|
|
9
|
+
comment,
|
|
10
|
+
destination,
|
|
11
|
+
opcode,
|
|
12
|
+
sender,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"BlockScanner",
|
|
18
|
+
"BlockEvent",
|
|
19
|
+
"TransactionEvent",
|
|
20
|
+
"TransactionsEvent",
|
|
21
|
+
"Where",
|
|
22
|
+
"comment",
|
|
23
|
+
"destination",
|
|
24
|
+
"opcode",
|
|
25
|
+
"sender",
|
|
26
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
from tonutils.tools.block_scanner.events import (
|
|
4
|
+
BlockEvent,
|
|
5
|
+
EventBase,
|
|
6
|
+
TransactionEvent,
|
|
7
|
+
TransactionsEvent,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
TEvent = t.TypeVar("TEvent", bound=EventBase)
|
|
11
|
+
|
|
12
|
+
Handler = t.Callable[[TEvent], t.Awaitable[None]]
|
|
13
|
+
Where = t.Callable[[TEvent], t.Union[bool, t.Awaitable[bool]]]
|
|
14
|
+
|
|
15
|
+
BlockWhere = t.Callable[[BlockEvent], t.Union[bool, t.Awaitable[bool]]]
|
|
16
|
+
TransactionWhere = t.Callable[[TransactionEvent], t.Union[bool, t.Awaitable[bool]]]
|
|
17
|
+
TransactionsWhere = t.Callable[[TransactionsEvent], t.Union[bool, t.Awaitable[bool]]]
|
|
18
|
+
|
|
19
|
+
AnyHandler = t.Callable[[EventBase], t.Awaitable[None]]
|
|
20
|
+
AnyWhere = t.Callable[[EventBase], t.Union[bool, t.Awaitable[bool]]]
|
|
21
|
+
|
|
22
|
+
HandlerEntry = t.Tuple[AnyHandler, t.Optional[AnyWhere]]
|
|
23
|
+
Decorator = t.Callable[[Handler[TEvent]], Handler[TEvent]]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import traceback
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from tonutils.tools.block_scanner.annotations import (
|
|
7
|
+
AnyHandler,
|
|
8
|
+
AnyWhere,
|
|
9
|
+
Decorator,
|
|
10
|
+
Handler,
|
|
11
|
+
HandlerEntry,
|
|
12
|
+
TEvent,
|
|
13
|
+
Where,
|
|
14
|
+
)
|
|
15
|
+
from tonutils.tools.block_scanner.events import EventBase
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EventDispatcher:
|
|
19
|
+
"""Dispatches events to registered handlers asynchronously."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, max_concurrency: int = 1000) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Initialize EventDispatcher.
|
|
24
|
+
|
|
25
|
+
:param max_concurrency: maximum number of concurrent handler tasks.
|
|
26
|
+
"""
|
|
27
|
+
self._handlers: t.Dict[t.Type[EventBase], t.List[HandlerEntry]] = {}
|
|
28
|
+
self._sem = asyncio.Semaphore(max(1, max_concurrency))
|
|
29
|
+
self._tasks: t.Set[asyncio.Task[None]] = set()
|
|
30
|
+
self._closed = False
|
|
31
|
+
|
|
32
|
+
def register(
|
|
33
|
+
self,
|
|
34
|
+
event_type: t.Type[TEvent],
|
|
35
|
+
handler: Handler[TEvent],
|
|
36
|
+
*,
|
|
37
|
+
where: t.Optional[Where[TEvent]] = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Register a handler for a specific event type.
|
|
41
|
+
|
|
42
|
+
:param event_type: subclass of EventBase to handle.
|
|
43
|
+
:param handler: callable receiving the event.
|
|
44
|
+
:param where: optional filter predicate. Handler is invoked only if predicate returns True.
|
|
45
|
+
"""
|
|
46
|
+
if not callable(handler):
|
|
47
|
+
raise TypeError("handler must be callable")
|
|
48
|
+
|
|
49
|
+
entry: HandlerEntry = (
|
|
50
|
+
t.cast(AnyHandler, handler),
|
|
51
|
+
t.cast(t.Optional[AnyWhere], where),
|
|
52
|
+
)
|
|
53
|
+
self._handlers.setdefault(event_type, []).append(entry)
|
|
54
|
+
|
|
55
|
+
def on(
|
|
56
|
+
self,
|
|
57
|
+
event_type: t.Type[TEvent],
|
|
58
|
+
*,
|
|
59
|
+
where: t.Optional[Where[TEvent]] = None,
|
|
60
|
+
) -> Decorator[TEvent]:
|
|
61
|
+
"""
|
|
62
|
+
Decorator to register a handler for an event type.
|
|
63
|
+
|
|
64
|
+
:param event_type: event class to handle.
|
|
65
|
+
:param where: optional filter predicate.
|
|
66
|
+
:return: Decorator that registers the handler.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def decorator(fn: Handler[TEvent]) -> Handler[TEvent]:
|
|
70
|
+
self.register(event_type=event_type, handler=fn, where=where)
|
|
71
|
+
return fn
|
|
72
|
+
|
|
73
|
+
return decorator
|
|
74
|
+
|
|
75
|
+
def _iter_handlers(self, event: EventBase) -> t.Sequence[HandlerEntry]:
|
|
76
|
+
"""Return all handlers matching the type of `event`."""
|
|
77
|
+
out: t.List[HandlerEntry] = []
|
|
78
|
+
for tp in type(event).mro():
|
|
79
|
+
if tp is EventBase:
|
|
80
|
+
break
|
|
81
|
+
entries = self._handlers.get(t.cast(t.Type[EventBase], tp))
|
|
82
|
+
if entries:
|
|
83
|
+
out.extend(entries)
|
|
84
|
+
return out
|
|
85
|
+
|
|
86
|
+
def _on_task_done(self, task: asyncio.Task[None]) -> None:
|
|
87
|
+
"""Callback to handle task completion and print exceptions."""
|
|
88
|
+
self._tasks.discard(task)
|
|
89
|
+
try:
|
|
90
|
+
exc = task.exception()
|
|
91
|
+
except asyncio.CancelledError:
|
|
92
|
+
return
|
|
93
|
+
if exc is not None:
|
|
94
|
+
traceback.print_exception(type(exc), exc, exc.__traceback__)
|
|
95
|
+
|
|
96
|
+
async def _run_task(
|
|
97
|
+
self,
|
|
98
|
+
handler: AnyHandler,
|
|
99
|
+
event: EventBase,
|
|
100
|
+
where: t.Optional[AnyWhere] = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Run a single handler task with optional 'where' filtering.
|
|
104
|
+
|
|
105
|
+
:param handler: async callable to execute.
|
|
106
|
+
:param event: event instance to pass.
|
|
107
|
+
:param where: optional predicate, skip handler if False.
|
|
108
|
+
"""
|
|
109
|
+
async with self._sem:
|
|
110
|
+
if where is not None:
|
|
111
|
+
result = where(event)
|
|
112
|
+
if inspect.isawaitable(result):
|
|
113
|
+
result = await result
|
|
114
|
+
if not result:
|
|
115
|
+
return
|
|
116
|
+
await handler(event)
|
|
117
|
+
|
|
118
|
+
def emit(self, event: EventBase) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Emit an event to all matching handlers.
|
|
121
|
+
|
|
122
|
+
Handlers are executed asynchronously.
|
|
123
|
+
"""
|
|
124
|
+
if self._closed:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
for handler, where in self._iter_handlers(event):
|
|
128
|
+
task = asyncio.create_task(self._run_task(handler, event, where))
|
|
129
|
+
self._tasks.add(task)
|
|
130
|
+
task.add_done_callback(self._on_task_done)
|
|
131
|
+
|
|
132
|
+
async def aclose(self) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Close the dispatcher and wait for all running handler tasks.
|
|
135
|
+
|
|
136
|
+
After calling, no new events will be dispatched.
|
|
137
|
+
"""
|
|
138
|
+
self._closed = True
|
|
139
|
+
if self._tasks:
|
|
140
|
+
await asyncio.gather(*self._tasks, return_exceptions=True)
|
|
141
|
+
self._tasks.clear()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from pytoniq_core import Transaction
|
|
5
|
+
from pytoniq_core.tl import BlockIdExt
|
|
6
|
+
|
|
7
|
+
from tonutils.clients import LiteBalancer, LiteClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class EventBase:
|
|
12
|
+
client: t.Union[LiteBalancer, LiteClient]
|
|
13
|
+
mc_block: BlockIdExt
|
|
14
|
+
context: t.Dict[str, t.Any]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class BlockEvent(EventBase):
|
|
19
|
+
block: BlockIdExt
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True, slots=True)
|
|
23
|
+
class TransactionEvent(EventBase):
|
|
24
|
+
block: BlockIdExt
|
|
25
|
+
transaction: Transaction
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class TransactionsEvent(EventBase):
|
|
30
|
+
block: BlockIdExt
|
|
31
|
+
transactions: t.List[Transaction] = field(default_factory=list)
|