htcli 1.1.0__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.
- htcli-1.1.0.dist-info/METADATA +509 -0
- htcli-1.1.0.dist-info/RECORD +140 -0
- htcli-1.1.0.dist-info/WHEEL +4 -0
- htcli-1.1.0.dist-info/entry_points.txt +2 -0
- htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
- src/__init__.py +0 -0
- src/htcli/__init__.py +5 -0
- src/htcli/client/__init__.py +338 -0
- src/htcli/client/extrinsics/__init__.py +26 -0
- src/htcli/client/extrinsics/base.py +487 -0
- src/htcli/client/extrinsics/consensus.py +79 -0
- src/htcli/client/extrinsics/governance.py +714 -0
- src/htcli/client/extrinsics/identity.py +490 -0
- src/htcli/client/extrinsics/node.py +1054 -0
- src/htcli/client/extrinsics/overwatch.py +401 -0
- src/htcli/client/extrinsics/staking.py +1504 -0
- src/htcli/client/extrinsics/subnet.py +2218 -0
- src/htcli/client/extrinsics/validator.py +203 -0
- src/htcli/client/extrinsics/wallet.py +323 -0
- src/htcli/client/offchain/__init__.py +10 -0
- src/htcli/client/offchain/backup.py +385 -0
- src/htcli/client/offchain/config.py +541 -0
- src/htcli/client/offchain/wallet.py +839 -0
- src/htcli/client/rpc/__init__.py +20 -0
- src/htcli/client/rpc/chain.py +568 -0
- src/htcli/client/rpc/node.py +783 -0
- src/htcli/client/rpc/overwatch.py +680 -0
- src/htcli/client/rpc/staking.py +216 -0
- src/htcli/client/rpc/subnet.py +2104 -0
- src/htcli/client/rpc/wallet.py +912 -0
- src/htcli/commands/__init__.py +31 -0
- src/htcli/commands/chain/__init__.py +66 -0
- src/htcli/commands/chain/display.py +204 -0
- src/htcli/commands/chain/handlers.py +260 -0
- src/htcli/commands/config/__init__.py +158 -0
- src/htcli/commands/config/display.py +353 -0
- src/htcli/commands/config/handlers.py +347 -0
- src/htcli/commands/config/prompts.py +357 -0
- src/htcli/commands/consensus/__init__.py +61 -0
- src/htcli/commands/consensus/handlers.py +100 -0
- src/htcli/commands/governance/__init__.py +49 -0
- src/htcli/commands/governance/handlers.py +81 -0
- src/htcli/commands/node/__init__.py +304 -0
- src/htcli/commands/node/display.py +749 -0
- src/htcli/commands/node/error_handling.py +470 -0
- src/htcli/commands/node/handlers.py +844 -0
- src/htcli/commands/node/prompts.py +346 -0
- src/htcli/commands/overwatch/__init__.py +219 -0
- src/htcli/commands/overwatch/display.py +396 -0
- src/htcli/commands/overwatch/error_handling.py +276 -0
- src/htcli/commands/overwatch/handlers.py +443 -0
- src/htcli/commands/overwatch/prompts.py +359 -0
- src/htcli/commands/stake/__init__.py +736 -0
- src/htcli/commands/stake/display.py +1103 -0
- src/htcli/commands/stake/error_handling.py +425 -0
- src/htcli/commands/stake/handlers.py +1902 -0
- src/htcli/commands/stake/prompts.py +1080 -0
- src/htcli/commands/subnet/__init__.py +639 -0
- src/htcli/commands/subnet/display.py +801 -0
- src/htcli/commands/subnet/error_handling.py +524 -0
- src/htcli/commands/subnet/handlers.py +2855 -0
- src/htcli/commands/subnet/prompts.py +1225 -0
- src/htcli/commands/validator/__init__.py +192 -0
- src/htcli/commands/validator/display.py +54 -0
- src/htcli/commands/validator/handlers.py +340 -0
- src/htcli/commands/wallet/__init__.py +546 -0
- src/htcli/commands/wallet/display.py +806 -0
- src/htcli/commands/wallet/error_handling.py +210 -0
- src/htcli/commands/wallet/handlers.py +3040 -0
- src/htcli/commands/wallet/prompts.py +1518 -0
- src/htcli/config.py +184 -0
- src/htcli/dependencies.py +186 -0
- src/htcli/errors/__init__.py +63 -0
- src/htcli/errors/base.py +141 -0
- src/htcli/errors/display.py +20 -0
- src/htcli/errors/handlers.py +710 -0
- src/htcli/main.py +343 -0
- src/htcli/models/__init__.py +21 -0
- src/htcli/models/enums/enum_types.py +35 -0
- src/htcli/models/errors.py +103 -0
- src/htcli/models/requests/__init__.py +197 -0
- src/htcli/models/requests/config.py +70 -0
- src/htcli/models/requests/consensus.py +19 -0
- src/htcli/models/requests/governance.py +38 -0
- src/htcli/models/requests/identity.py +51 -0
- src/htcli/models/requests/key.py +22 -0
- src/htcli/models/requests/node.py +91 -0
- src/htcli/models/requests/overwatch.py +64 -0
- src/htcli/models/requests/staking.py +580 -0
- src/htcli/models/requests/subnet.py +195 -0
- src/htcli/models/requests/validator.py +139 -0
- src/htcli/models/requests/wallet.py +118 -0
- src/htcli/models/responses/__init__.py +147 -0
- src/htcli/models/responses/base.py +18 -0
- src/htcli/models/responses/chain.py +39 -0
- src/htcli/models/responses/config.py +58 -0
- src/htcli/models/responses/identity.py +102 -0
- src/htcli/models/responses/overwatch.py +51 -0
- src/htcli/models/responses/staking.py +502 -0
- src/htcli/models/responses/subnet.py +856 -0
- src/htcli/models/responses/wallet.py +185 -0
- src/htcli/ui/__init__.py +87 -0
- src/htcli/ui/colors.py +309 -0
- src/htcli/ui/components/__init__.py +60 -0
- src/htcli/ui/components/panels.py +174 -0
- src/htcli/ui/components/progress.py +166 -0
- src/htcli/ui/components/spinners.py +92 -0
- src/htcli/ui/components/tables.py +809 -0
- src/htcli/ui/components/trees.py +721 -0
- src/htcli/ui/display.py +336 -0
- src/htcli/ui/prompts.py +870 -0
- src/htcli/utils/__init__.py +76 -0
- src/htcli/utils/blockchain/__init__.py +75 -0
- src/htcli/utils/blockchain/formatting.py +368 -0
- src/htcli/utils/blockchain/patches.py +286 -0
- src/htcli/utils/blockchain/peer_id.py +186 -0
- src/htcli/utils/blockchain/staking.py +448 -0
- src/htcli/utils/blockchain/type_registry.py +1373 -0
- src/htcli/utils/blockchain/validation.py +179 -0
- src/htcli/utils/cache.py +613 -0
- src/htcli/utils/constants.py +38 -0
- src/htcli/utils/legacy/__init__.py +12 -0
- src/htcli/utils/legacy/colors.py +311 -0
- src/htcli/utils/legacy/crypto.py +1176 -0
- src/htcli/utils/legacy/formatting.py +452 -0
- src/htcli/utils/legacy/interactive.py +306 -0
- src/htcli/utils/legacy/subnet_manifest.py +265 -0
- src/htcli/utils/legacy/validation.py +488 -0
- src/htcli/utils/logging.py +183 -0
- src/htcli/utils/network/__init__.py +20 -0
- src/htcli/utils/network/subnet.py +344 -0
- src/htcli/utils/prompts.py +27 -0
- src/htcli/utils/scale_codec.py +155 -0
- src/htcli/utils/validation/__init__.py +57 -0
- src/htcli/utils/validation/prompt_validators.py +267 -0
- src/htcli/utils/wallet/__init__.py +65 -0
- src/htcli/utils/wallet/auth.py +151 -0
- src/htcli/utils/wallet/core.py +1069 -0
- src/htcli/utils/wallet/crypto.py +1615 -0
- src/htcli/utils/wallet/migration.py +159 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RPC module for read-only blockchain queries.
|
|
3
|
+
Contains clients for querying blockchain state without making changes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .chain import ChainRpcClient
|
|
7
|
+
from .node import SubnetNodeRpcClient
|
|
8
|
+
from .overwatch import OverwatchRpcClient
|
|
9
|
+
from .staking import StakingRpcClient
|
|
10
|
+
from .subnet import SubnetRpcClient
|
|
11
|
+
from .wallet import WalletRpcClient
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ChainRpcClient",
|
|
15
|
+
"StakingRpcClient",
|
|
16
|
+
"SubnetRpcClient",
|
|
17
|
+
"WalletRpcClient",
|
|
18
|
+
"SubnetNodeRpcClient",
|
|
19
|
+
"OverwatchRpcClient",
|
|
20
|
+
]
|
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RPC client for general chain queries.
|
|
3
|
+
Handles network peers, block info, chain head, and runtime version queries.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
from substrateinterface import SubstrateInterface
|
|
11
|
+
|
|
12
|
+
from ...utils.logging import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ChainRpcClient:
|
|
18
|
+
"""RPC client for general chain read-only operations."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, substrate: Optional[SubstrateInterface] = None):
|
|
21
|
+
"""Initialize the chain RPC client."""
|
|
22
|
+
self.substrate = substrate
|
|
23
|
+
|
|
24
|
+
def get_block_number(self, block_hash: Optional[str] = None) -> Optional[int]:
|
|
25
|
+
"""
|
|
26
|
+
Get the block number for the supplied hash (defaults to latest).
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
block_hash: Optional block hash to resolve. When omitted, the latest block
|
|
30
|
+
hash is fetched first (matching mesh-template chain_functions).
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Current block number or None if error
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
if not self.substrate:
|
|
37
|
+
raise Exception("Not connected to blockchain")
|
|
38
|
+
|
|
39
|
+
# If we already have a block hash, resolve it directly
|
|
40
|
+
if block_hash:
|
|
41
|
+
return self.substrate.get_block_number(block_hash=block_hash)
|
|
42
|
+
|
|
43
|
+
# Otherwise, fetch the latest block hash and resolve that
|
|
44
|
+
latest_hash = self.substrate.get_block_hash()
|
|
45
|
+
if latest_hash:
|
|
46
|
+
return self.substrate.get_block_number(block_hash=latest_hash)
|
|
47
|
+
|
|
48
|
+
# Fallback to querying the latest header (should rarely be needed)
|
|
49
|
+
header = self.substrate.rpc_request("chain_getHeader", [None])
|
|
50
|
+
if header and "result" in header and header["result"].get("number"):
|
|
51
|
+
return int(header["result"]["number"], 16)
|
|
52
|
+
|
|
53
|
+
return None
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.error(f"Failed to get block number: {str(e)}")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def get_current_epoch(self) -> Optional[int]:
|
|
59
|
+
"""
|
|
60
|
+
Get current epoch number.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Current epoch or None if error
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
if not self.substrate:
|
|
67
|
+
raise Exception("Not connected to blockchain")
|
|
68
|
+
|
|
69
|
+
# Get current block
|
|
70
|
+
block_num = self.get_block_number()
|
|
71
|
+
if block_num is None:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
# Get epoch length from runtime constant
|
|
75
|
+
epoch_length = self.substrate.get_constant("Network", "EpochLength")
|
|
76
|
+
if epoch_length:
|
|
77
|
+
return block_num // epoch_length.value
|
|
78
|
+
return None
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(f"Failed to get current epoch: {str(e)}")
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def get_account_balance(self, address: str) -> Optional[int]:
|
|
84
|
+
"""
|
|
85
|
+
Get account balance in wei.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
address: Account address
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Balance in wei or None if error
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
if not self.substrate:
|
|
95
|
+
raise Exception("Not connected to blockchain")
|
|
96
|
+
|
|
97
|
+
# Query account info
|
|
98
|
+
result = self.substrate.query("System", "Account", [address])
|
|
99
|
+
if result and hasattr(result, "value") and result.value:
|
|
100
|
+
account_info = result.value
|
|
101
|
+
# Get free balance
|
|
102
|
+
if isinstance(account_info, dict) and "data" in account_info:
|
|
103
|
+
return account_info["data"].get("free", 0)
|
|
104
|
+
return 0
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"Failed to get account balance: {str(e)}")
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
def get_peers(self) -> dict[str, Any]:
|
|
110
|
+
"""
|
|
111
|
+
Get network peers using RPC call.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
dictionary with peers information
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
if not self.substrate:
|
|
118
|
+
raise Exception("Not connected to blockchain")
|
|
119
|
+
|
|
120
|
+
peers = self.substrate.rpc_request("system_peers", [])
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"success": True,
|
|
124
|
+
"message": "Peers retrieved successfully",
|
|
125
|
+
"data": peers.get("result", []) if peers else [],
|
|
126
|
+
}
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.error(f"Failed to get peers: {str(e)}")
|
|
129
|
+
return {
|
|
130
|
+
"success": False,
|
|
131
|
+
"message": f"Failed to get peers: {str(e)}",
|
|
132
|
+
"data": [],
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
def get_block_info(self, block_number: Optional[int] = None) -> dict[str, Any]:
|
|
136
|
+
"""
|
|
137
|
+
Get block information using RPC calls.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
block_number: Optional block number to query (defaults to latest)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
dictionary with block information
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
if not self.substrate:
|
|
147
|
+
raise Exception("Not connected to blockchain")
|
|
148
|
+
|
|
149
|
+
# Get block hash
|
|
150
|
+
if block_number is not None:
|
|
151
|
+
block_hash = self.substrate.get_block_hash(block_number)
|
|
152
|
+
else:
|
|
153
|
+
block_hash = self.substrate.get_chain_head()
|
|
154
|
+
|
|
155
|
+
# Get block header and data
|
|
156
|
+
block_header = self.substrate.get_block_header(block_hash)
|
|
157
|
+
block = self.substrate.get_block(block_hash)
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
"success": True,
|
|
161
|
+
"message": "Block info retrieved successfully",
|
|
162
|
+
"data": {
|
|
163
|
+
"hash": str(block_hash),
|
|
164
|
+
"number": block_header.get("number", 0) if block_header else 0,
|
|
165
|
+
"parent_hash": (
|
|
166
|
+
str(block_header.get("parentHash", "")) if block_header else ""
|
|
167
|
+
),
|
|
168
|
+
"state_root": (
|
|
169
|
+
str(block_header.get("stateRoot", "")) if block_header else ""
|
|
170
|
+
),
|
|
171
|
+
"extrinsics_root": (
|
|
172
|
+
str(block_header.get("extrinsicsRoot", ""))
|
|
173
|
+
if block_header
|
|
174
|
+
else ""
|
|
175
|
+
),
|
|
176
|
+
"digest": block_header.get("digest", {}) if block_header else {},
|
|
177
|
+
"extrinsics_count": (
|
|
178
|
+
len(block.get("extrinsics", [])) if block else 0
|
|
179
|
+
),
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"Failed to get block info: {str(e)}")
|
|
184
|
+
return {
|
|
185
|
+
"success": False,
|
|
186
|
+
"message": f"Failed to get block info: {str(e)}",
|
|
187
|
+
"data": {},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
def get_chain_head(self) -> dict[str, Any]:
|
|
191
|
+
"""
|
|
192
|
+
Get the current chain head.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
dictionary with chain head information
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
if not self.substrate:
|
|
199
|
+
raise Exception("Not connected to blockchain")
|
|
200
|
+
|
|
201
|
+
head = self.substrate.get_chain_head()
|
|
202
|
+
return {
|
|
203
|
+
"success": True,
|
|
204
|
+
"message": "Chain head retrieved successfully",
|
|
205
|
+
"data": {"head": str(head)},
|
|
206
|
+
}
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"Failed to get chain head: {str(e)}")
|
|
209
|
+
return {
|
|
210
|
+
"success": False,
|
|
211
|
+
"message": f"Failed to get chain head: {str(e)}",
|
|
212
|
+
"data": {"head": None},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
def get_runtime_version(self) -> dict[str, Any]:
|
|
216
|
+
"""
|
|
217
|
+
Get the runtime version.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
dictionary with runtime version information
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
if not self.substrate:
|
|
224
|
+
raise Exception("Not connected to blockchain")
|
|
225
|
+
|
|
226
|
+
runtime_version = self.substrate.get_runtime_version()
|
|
227
|
+
return {
|
|
228
|
+
"success": True,
|
|
229
|
+
"message": "Runtime version retrieved successfully",
|
|
230
|
+
"data": runtime_version,
|
|
231
|
+
}
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error(f"Failed to get runtime version: {str(e)}")
|
|
234
|
+
return {
|
|
235
|
+
"success": False,
|
|
236
|
+
"message": f"Failed to get runtime version: {str(e)}",
|
|
237
|
+
"data": {},
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
def get_network_properties(self) -> dict[str, Any]:
|
|
241
|
+
"""
|
|
242
|
+
Get network properties using RPC call.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
dictionary with network properties
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
if not self.substrate:
|
|
249
|
+
raise Exception("Not connected to blockchain")
|
|
250
|
+
|
|
251
|
+
properties = self.substrate.rpc_request("system_properties", [])
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
"success": True,
|
|
255
|
+
"message": "Network properties retrieved successfully",
|
|
256
|
+
"data": properties.get("result", {}) if properties else {},
|
|
257
|
+
}
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error(f"Failed to get network properties: {str(e)}")
|
|
260
|
+
return {
|
|
261
|
+
"success": False,
|
|
262
|
+
"message": f"Failed to get network properties: {str(e)}",
|
|
263
|
+
"data": {},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
def get_chain_spec(self) -> dict[str, Any]:
|
|
267
|
+
"""
|
|
268
|
+
Get chain specification using RPC call.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
dictionary with chain specification
|
|
272
|
+
"""
|
|
273
|
+
try:
|
|
274
|
+
if not self.substrate:
|
|
275
|
+
raise Exception("Not connected to blockchain")
|
|
276
|
+
|
|
277
|
+
chain_spec = self.substrate.rpc_request("system_chain", [])
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
"success": True,
|
|
281
|
+
"message": "Chain spec retrieved successfully",
|
|
282
|
+
"data": {
|
|
283
|
+
"chain": chain_spec.get("result", "") if chain_spec else "",
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Failed to get chain spec: {str(e)}")
|
|
288
|
+
return {
|
|
289
|
+
"success": False,
|
|
290
|
+
"message": f"Failed to get chain spec: {str(e)}",
|
|
291
|
+
"data": {"chain": ""},
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
def get_health(self) -> dict[str, Any]:
|
|
295
|
+
"""
|
|
296
|
+
Get network health status using RPC call.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
dictionary with health information
|
|
300
|
+
"""
|
|
301
|
+
try:
|
|
302
|
+
if not self.substrate:
|
|
303
|
+
raise Exception("Not connected to blockchain")
|
|
304
|
+
|
|
305
|
+
health = self.substrate.rpc_request("system_health", [])
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
"success": True,
|
|
309
|
+
"message": "Health status retrieved successfully",
|
|
310
|
+
"data": health.get("result", {}) if health else {},
|
|
311
|
+
}
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.error(f"Failed to get health status: {str(e)}")
|
|
314
|
+
return {
|
|
315
|
+
"success": False,
|
|
316
|
+
"message": f"Failed to get health status: {str(e)}",
|
|
317
|
+
"data": {},
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
def ping(self) -> dict[str, Any]:
|
|
321
|
+
"""
|
|
322
|
+
Ping the connected node with a lightweight health RPC.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
dictionary with latency and health details
|
|
326
|
+
"""
|
|
327
|
+
start_time = time.perf_counter()
|
|
328
|
+
try:
|
|
329
|
+
if not self.substrate:
|
|
330
|
+
raise Exception("Not connected to blockchain")
|
|
331
|
+
|
|
332
|
+
health_response = self.substrate.rpc_request("system_health", [])
|
|
333
|
+
latency_ms = (time.perf_counter() - start_time) * 1000
|
|
334
|
+
health = health_response.get("result", {}) if health_response else {}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
"success": True,
|
|
338
|
+
"message": "Node responded successfully",
|
|
339
|
+
"data": {
|
|
340
|
+
"latency_ms": latency_ms,
|
|
341
|
+
"peers": health.get("peers"),
|
|
342
|
+
"is_syncing": health.get("isSyncing"),
|
|
343
|
+
"should_have_peers": health.get("shouldHavePeers"),
|
|
344
|
+
},
|
|
345
|
+
}
|
|
346
|
+
except Exception as e:
|
|
347
|
+
latency_ms = (time.perf_counter() - start_time) * 1000
|
|
348
|
+
logger.error(f"Failed to ping node: {str(e)}")
|
|
349
|
+
return {
|
|
350
|
+
"success": False,
|
|
351
|
+
"message": f"Failed to ping node: {str(e)}",
|
|
352
|
+
"data": {"latency_ms": latency_ms},
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
def get_sync_state(self) -> dict[str, Any]:
|
|
356
|
+
"""
|
|
357
|
+
Get synchronization state using RPC call.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
dictionary with sync state information
|
|
361
|
+
"""
|
|
362
|
+
try:
|
|
363
|
+
if not self.substrate:
|
|
364
|
+
raise Exception("Not connected to blockchain")
|
|
365
|
+
|
|
366
|
+
sync_state = self.substrate.rpc_request("system_syncState", [])
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
"success": True,
|
|
370
|
+
"message": "Sync state retrieved successfully",
|
|
371
|
+
"data": sync_state.get("result", {}) if sync_state else {},
|
|
372
|
+
}
|
|
373
|
+
except Exception as e:
|
|
374
|
+
logger.error(f"Failed to get sync state: {str(e)}")
|
|
375
|
+
return {
|
|
376
|
+
"success": False,
|
|
377
|
+
"message": f"Failed to get sync state: {str(e)}",
|
|
378
|
+
"data": {},
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
def _percent_mul(self, x: int, y: int) -> int:
|
|
382
|
+
"""
|
|
383
|
+
Multiply x by percentage y (using 1e18 precision).
|
|
384
|
+
|
|
385
|
+
Equivalent to Rust: x * y / PERCENTAGE_FACTOR
|
|
386
|
+
"""
|
|
387
|
+
PERCENTAGE_FACTOR = 1_000_000_000_000_000_000 # 1e18
|
|
388
|
+
|
|
389
|
+
if x == 0 or y == 0:
|
|
390
|
+
return 0
|
|
391
|
+
|
|
392
|
+
# Use integer arithmetic to avoid precision loss
|
|
393
|
+
result = (x * y) // PERCENTAGE_FACTOR
|
|
394
|
+
return min(result, 2**127 - 1) # Clamp to safe u128 range
|
|
395
|
+
|
|
396
|
+
def _percent_div(self, x: int, y: int) -> int:
|
|
397
|
+
"""
|
|
398
|
+
Divide x by y, returning result as percentage (using 1e18 precision).
|
|
399
|
+
|
|
400
|
+
Equivalent to Rust: x * PERCENTAGE_FACTOR / y
|
|
401
|
+
"""
|
|
402
|
+
PERCENTAGE_FACTOR = 1_000_000_000_000_000_000 # 1e18
|
|
403
|
+
|
|
404
|
+
if x == 0 or y == 0:
|
|
405
|
+
return 0
|
|
406
|
+
|
|
407
|
+
# Use integer arithmetic to avoid precision loss
|
|
408
|
+
result = (x * PERCENTAGE_FACTOR) // y
|
|
409
|
+
return min(result, 2**127 - 1) # Clamp to safe u128 range
|
|
410
|
+
|
|
411
|
+
def _get_percent_as_f64(self, v: int) -> float:
|
|
412
|
+
"""
|
|
413
|
+
Convert percentage value (1e18 = 100%) to f64.
|
|
414
|
+
|
|
415
|
+
Equivalent to Rust: v as f64 / PERCENTAGE_FACTOR
|
|
416
|
+
"""
|
|
417
|
+
PERCENTAGE_FACTOR = 1_000_000_000_000_000_000 # 1e18
|
|
418
|
+
return v / PERCENTAGE_FACTOR
|
|
419
|
+
|
|
420
|
+
def get_subnet_registration_cost(self) -> Optional[int]:
|
|
421
|
+
"""
|
|
422
|
+
Get the current subnet registration cost in wei.
|
|
423
|
+
|
|
424
|
+
The cost dynamically decreases over time using exponential decay
|
|
425
|
+
from the last registration until it reaches the minimum cost.
|
|
426
|
+
|
|
427
|
+
This queries storage values and calculates the current cost based on:
|
|
428
|
+
- Last registration cost
|
|
429
|
+
- Minimum cost
|
|
430
|
+
- Decay period and rate
|
|
431
|
+
- Blocks since last registration
|
|
432
|
+
- Alpha parameter for exponential decay
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Current registration cost in wei (1e18 = 1 TENSOR) or None if error
|
|
436
|
+
|
|
437
|
+
Example:
|
|
438
|
+
>>> chain_rpc = ChainRpcClient(substrate)
|
|
439
|
+
>>> cost = chain_rpc.get_subnet_registration_cost()
|
|
440
|
+
>>> if cost:
|
|
441
|
+
>>> print(f"Current cost: {cost / 1e18:.2f} TENSOR")
|
|
442
|
+
"""
|
|
443
|
+
try:
|
|
444
|
+
if not self.substrate:
|
|
445
|
+
raise Exception("Not connected to blockchain")
|
|
446
|
+
|
|
447
|
+
# Query storage values with detailed error tracking
|
|
448
|
+
logger.debug("Querying LastRegistrationCost...")
|
|
449
|
+
last_cost_result = self.substrate.query(
|
|
450
|
+
"Network", "LastRegistrationCost", []
|
|
451
|
+
)
|
|
452
|
+
logger.debug(f"LastRegistrationCost result: {last_cost_result}")
|
|
453
|
+
|
|
454
|
+
logger.debug("Querying MinRegistrationCost...")
|
|
455
|
+
min_cost_result = self.substrate.query("Network", "MinRegistrationCost", [])
|
|
456
|
+
logger.debug(f"MinRegistrationCost result: {min_cost_result}")
|
|
457
|
+
|
|
458
|
+
logger.debug("Querying LastSubnetRegistrationBlock...")
|
|
459
|
+
last_block_result = self.substrate.query(
|
|
460
|
+
"Network", "LastSubnetRegistrationBlock", []
|
|
461
|
+
)
|
|
462
|
+
logger.debug(f"LastSubnetRegistrationBlock result: {last_block_result}")
|
|
463
|
+
|
|
464
|
+
logger.debug("Querying RegistrationCostDecayBlocks...")
|
|
465
|
+
decay_blocks_result = self.substrate.query(
|
|
466
|
+
"Network", "RegistrationCostDecayBlocks", []
|
|
467
|
+
)
|
|
468
|
+
logger.debug(f"RegistrationCostDecayBlocks result: {decay_blocks_result}")
|
|
469
|
+
|
|
470
|
+
logger.debug("Querying RegistrationCostAlpha...")
|
|
471
|
+
alpha_result = self.substrate.query("Network", "RegistrationCostAlpha", [])
|
|
472
|
+
logger.debug(f"RegistrationCostAlpha result: {alpha_result}")
|
|
473
|
+
|
|
474
|
+
# Check which values are missing
|
|
475
|
+
missing = []
|
|
476
|
+
if not last_cost_result:
|
|
477
|
+
missing.append("LastRegistrationCost")
|
|
478
|
+
if not min_cost_result:
|
|
479
|
+
missing.append("MinRegistrationCost")
|
|
480
|
+
if not last_block_result:
|
|
481
|
+
missing.append("LastSubnetRegistrationBlock")
|
|
482
|
+
if not decay_blocks_result:
|
|
483
|
+
missing.append("RegistrationCostDecayBlocks")
|
|
484
|
+
if not alpha_result:
|
|
485
|
+
missing.append("RegistrationCostAlpha")
|
|
486
|
+
|
|
487
|
+
if missing:
|
|
488
|
+
logger.error(f"Missing storage values: {', '.join(missing)}")
|
|
489
|
+
logger.error("These storage items may not exist in the Network pallet")
|
|
490
|
+
return None
|
|
491
|
+
|
|
492
|
+
last_cost = (
|
|
493
|
+
last_cost_result.value
|
|
494
|
+
if hasattr(last_cost_result, "value")
|
|
495
|
+
else last_cost_result
|
|
496
|
+
)
|
|
497
|
+
min_cost = (
|
|
498
|
+
min_cost_result.value
|
|
499
|
+
if hasattr(min_cost_result, "value")
|
|
500
|
+
else min_cost_result
|
|
501
|
+
)
|
|
502
|
+
last_block = (
|
|
503
|
+
last_block_result.value
|
|
504
|
+
if hasattr(last_block_result, "value")
|
|
505
|
+
else last_block_result
|
|
506
|
+
)
|
|
507
|
+
decay_blocks = (
|
|
508
|
+
decay_blocks_result.value
|
|
509
|
+
if hasattr(decay_blocks_result, "value")
|
|
510
|
+
else decay_blocks_result
|
|
511
|
+
)
|
|
512
|
+
alpha = (
|
|
513
|
+
alpha_result.value if hasattr(alpha_result, "value") else alpha_result
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Get current block
|
|
517
|
+
current_block = self.get_block_number()
|
|
518
|
+
if current_block is None:
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
# Calculate decayed cost using exponential decay (matching blockchain)
|
|
522
|
+
delta_blocks = current_block - last_block
|
|
523
|
+
|
|
524
|
+
# If already at min or no decay period
|
|
525
|
+
if decay_blocks == 0 or last_cost <= min_cost:
|
|
526
|
+
cost = max(last_cost, min_cost)
|
|
527
|
+
elif delta_blocks >= decay_blocks:
|
|
528
|
+
# Fully decayed: exactly min price
|
|
529
|
+
cost = min_cost
|
|
530
|
+
else:
|
|
531
|
+
# Exponential decay calculation (matching blockchain implementation)
|
|
532
|
+
diff = last_cost - min_cost
|
|
533
|
+
|
|
534
|
+
# Calculate remaining fraction as percentage: (decay_blocks - delta_blocks) / decay_blocks
|
|
535
|
+
remaining_blocks = decay_blocks - delta_blocks
|
|
536
|
+
remaining_frac = self._percent_div(remaining_blocks, decay_blocks)
|
|
537
|
+
|
|
538
|
+
# Apply concave exponential: remaining_frac ^ alpha
|
|
539
|
+
# Convert to f64 for pow calculation
|
|
540
|
+
remaining_frac_f64 = self._get_percent_as_f64(remaining_frac)
|
|
541
|
+
alpha_f64 = self._get_percent_as_f64(alpha)
|
|
542
|
+
|
|
543
|
+
# Calculate concave factor using pow
|
|
544
|
+
concave_factor = math.pow(remaining_frac_f64, alpha_f64)
|
|
545
|
+
|
|
546
|
+
# Calculate addend: diff * concave_factor
|
|
547
|
+
# Prevent overflow by clamping diff
|
|
548
|
+
safe_diff_f64 = min(diff, (2**127 - 1) / max(concave_factor, 1e-10))
|
|
549
|
+
addend = int(safe_diff_f64 * concave_factor)
|
|
550
|
+
addend = max(0, min(addend, 2**127 - 1)) # Clamp to safe range
|
|
551
|
+
|
|
552
|
+
# Final cost: min_price + addend
|
|
553
|
+
cost = min_cost + addend
|
|
554
|
+
cost = max(cost, min_cost) # Ensure cost >= min_cost
|
|
555
|
+
cost = min(cost, 2**127 - 1) # Clamp to safe u128 range
|
|
556
|
+
|
|
557
|
+
logger.info(
|
|
558
|
+
f"Subnet registration cost at block {current_block}: {cost / 1e18:.2f} TENSOR"
|
|
559
|
+
)
|
|
560
|
+
logger.debug(
|
|
561
|
+
f" Last cost: {last_cost / 1e18:.2f}, Min: {min_cost / 1e18:.2f}, "
|
|
562
|
+
f"Blocks since last: {delta_blocks}/{decay_blocks}, Alpha: {alpha / 1e18:.6f}"
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
return cost
|
|
566
|
+
except Exception as e:
|
|
567
|
+
logger.error(f"Failed to get subnet registration cost: {str(e)}")
|
|
568
|
+
return None
|