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,1054 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Subnet node management extrinsics for HTCLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from substrateinterface import Keypair, SubstrateInterface
|
|
8
|
+
|
|
9
|
+
from ...models.requests.node import (
|
|
10
|
+
SubnetNodeActivateRequest,
|
|
11
|
+
SubnetNodeBootnodePeerIdUpdateRequest,
|
|
12
|
+
SubnetNodeBootnodeUpdateRequest,
|
|
13
|
+
SubnetNodeClientPeerIdUpdateRequest,
|
|
14
|
+
SubnetNodePauseRequest,
|
|
15
|
+
SubnetNodePeerIdUpdateRequest,
|
|
16
|
+
SubnetNodeReactivateRequest,
|
|
17
|
+
SubnetNodeRegisterRequest,
|
|
18
|
+
SubnetNodeRemoveRequest,
|
|
19
|
+
SubnetNodeUpdateRequest,
|
|
20
|
+
)
|
|
21
|
+
from ...models.responses.subnet import SubnetNodeInfo
|
|
22
|
+
from ...utils.blockchain import validate_ethereum_address
|
|
23
|
+
from ...utils.logging import get_logger
|
|
24
|
+
from .base import BaseExtrinsicClient
|
|
25
|
+
from .subnet import _encode_multiaddr
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Simple response wrapper for methods that don't have specific response models
|
|
31
|
+
class SimpleExtrinsicResponse:
|
|
32
|
+
"""Simple wrapper for extrinsic responses."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, response_dict: dict):
|
|
35
|
+
self.success = response_dict.get("success", False)
|
|
36
|
+
self.error = response_dict.get("error")
|
|
37
|
+
self.transaction_hash = response_dict.get("extrinsic_hash")
|
|
38
|
+
self.block_number = response_dict.get("block_number")
|
|
39
|
+
self.block_hash = response_dict.get("block_hash")
|
|
40
|
+
self.message = response_dict.get("message")
|
|
41
|
+
self.subnet_id = response_dict.get("subnet_id")
|
|
42
|
+
self.validator_id = response_dict.get("validator_id")
|
|
43
|
+
self.subnet_node_id = response_dict.get("subnet_node_id")
|
|
44
|
+
self.hotkey = response_dict.get("hotkey")
|
|
45
|
+
self.stake_balance = response_dict.get("stake_balance")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NodeExtrinsics(BaseExtrinsicClient):
|
|
49
|
+
"""Client for subnet node-related extrinsics."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, substrate: Optional[SubstrateInterface] = None):
|
|
52
|
+
"""Initialize the subnet node client."""
|
|
53
|
+
super().__init__(substrate)
|
|
54
|
+
|
|
55
|
+
# ============================================================================
|
|
56
|
+
# SUBNET NODE REGISTRATION
|
|
57
|
+
# ============================================================================
|
|
58
|
+
|
|
59
|
+
def register_subnet_node(
|
|
60
|
+
self, request: SubnetNodeRegisterRequest, keypair: Keypair
|
|
61
|
+
) -> SimpleExtrinsicResponse:
|
|
62
|
+
"""
|
|
63
|
+
Register a new subnet node.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
request: Subnet node registration request
|
|
67
|
+
keypair: Keypair for signing the transaction
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
SimpleExtrinsicResponse with operation result
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
logger.info(f"Registering subnet node for subnet {request.subnet_id}")
|
|
74
|
+
|
|
75
|
+
# Validate inputs
|
|
76
|
+
self._validate_node_registration_request(request)
|
|
77
|
+
|
|
78
|
+
def peer_info(peer_id: str, multiaddr: Optional[str]) -> dict[str, Any]:
|
|
79
|
+
encoded_multiaddr = None
|
|
80
|
+
if multiaddr:
|
|
81
|
+
encoded_multiaddr = _encode_multiaddr(
|
|
82
|
+
f"{multiaddr.rstrip('/')}/p2p/{peer_id}"
|
|
83
|
+
)
|
|
84
|
+
return {
|
|
85
|
+
"peer_id": peer_id,
|
|
86
|
+
"multiaddr": encoded_multiaddr,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Prepare call parameters using the live validator-runtime metadata names.
|
|
90
|
+
call_params = {
|
|
91
|
+
"validator_id": request.validator_id,
|
|
92
|
+
"subnet_id": request.subnet_id,
|
|
93
|
+
"hotkey": request.hotkey,
|
|
94
|
+
"peer_info": peer_info(request.peer_id, request.bootnode),
|
|
95
|
+
"bootnode_peer_info": peer_info(
|
|
96
|
+
request.bootnode_peer_id, request.bootnode
|
|
97
|
+
),
|
|
98
|
+
"client_peer_info": peer_info(request.client_peer_id, None),
|
|
99
|
+
"stake_to_be_added": request.stake_to_be_added,
|
|
100
|
+
"unique": request.unique,
|
|
101
|
+
"non_unique": request.non_unique,
|
|
102
|
+
"max_burn_amount": request.max_burn_amount,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
call = self.substrate.compose_call(
|
|
106
|
+
call_module="Network",
|
|
107
|
+
call_function="register_subnet_node",
|
|
108
|
+
call_params=call_params,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
result = self._submit_extrinsic(call, keypair)
|
|
112
|
+
|
|
113
|
+
if result["success"]:
|
|
114
|
+
# Extract node ID from events
|
|
115
|
+
for event in result.get("events", []):
|
|
116
|
+
if hasattr(event, "value"):
|
|
117
|
+
module_id = event.value.get("module_id", "")
|
|
118
|
+
event_id = event.value.get("event_id", "")
|
|
119
|
+
|
|
120
|
+
# Node registration with stake triggers SubnetNodeActivated event
|
|
121
|
+
if module_id == "Network" and event_id in [
|
|
122
|
+
"SubnetNodeRegistered",
|
|
123
|
+
"SubnetNodeActivated",
|
|
124
|
+
]:
|
|
125
|
+
attributes = event.value.get("attributes", {})
|
|
126
|
+
|
|
127
|
+
# Handle both dict and list formats
|
|
128
|
+
if isinstance(attributes, dict):
|
|
129
|
+
result["subnet_node_id"] = attributes.get(
|
|
130
|
+
"subnet_node_id"
|
|
131
|
+
)
|
|
132
|
+
result["subnet_id"] = attributes.get(
|
|
133
|
+
"subnet_id", request.subnet_id
|
|
134
|
+
)
|
|
135
|
+
result["validator_id"] = attributes.get(
|
|
136
|
+
"validator_id", request.validator_id
|
|
137
|
+
)
|
|
138
|
+
result["hotkey"] = attributes.get("hotkey")
|
|
139
|
+
elif (
|
|
140
|
+
isinstance(attributes, (list, tuple))
|
|
141
|
+
and len(attributes) >= 2
|
|
142
|
+
):
|
|
143
|
+
result["subnet_id"] = attributes[0]
|
|
144
|
+
result["subnet_node_id"] = attributes[1]
|
|
145
|
+
if len(attributes) > 2:
|
|
146
|
+
result["hotkey"] = attributes[2]
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
# Add stake balance and hotkey from request if not found in events
|
|
150
|
+
if "validator_id" not in result:
|
|
151
|
+
result["validator_id"] = request.validator_id
|
|
152
|
+
if "stake_balance" not in result:
|
|
153
|
+
result["stake_balance"] = request.stake_to_be_added
|
|
154
|
+
if "hotkey" not in result or not result["hotkey"]:
|
|
155
|
+
result["hotkey"] = request.hotkey
|
|
156
|
+
|
|
157
|
+
result["message"] = (
|
|
158
|
+
f"Subnet node registered successfully for subnet {request.subnet_id}"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return SimpleExtrinsicResponse(result)
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(f"Error registering subnet node: {e}")
|
|
165
|
+
return SimpleExtrinsicResponse({"success": False, "error": str(e)})
|
|
166
|
+
|
|
167
|
+
# ============================================================================
|
|
168
|
+
# SUBNET NODE ACTIVATION
|
|
169
|
+
# ============================================================================
|
|
170
|
+
|
|
171
|
+
def activate_subnet_node(
|
|
172
|
+
self, request: SubnetNodeActivateRequest, keypair: Keypair
|
|
173
|
+
) -> SimpleExtrinsicResponse:
|
|
174
|
+
"""
|
|
175
|
+
Activate a subnet node.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
request: Subnet node activation request
|
|
179
|
+
keypair: Keypair for signing the transaction
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Dictionary with operation result
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
logger.info(
|
|
186
|
+
f"Activating subnet {request.subnet_id} node {request.subnet_node_id}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
call = self.substrate.compose_call(
|
|
190
|
+
call_module="Network",
|
|
191
|
+
call_function="activate_subnet_node",
|
|
192
|
+
call_params={
|
|
193
|
+
"subnet_id": request.subnet_id,
|
|
194
|
+
"subnet_node_id": request.subnet_node_id,
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
result = self._submit_extrinsic(call, keypair)
|
|
199
|
+
|
|
200
|
+
if result["success"]:
|
|
201
|
+
result["message"] = (
|
|
202
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} activated successfully"
|
|
203
|
+
)
|
|
204
|
+
result["subnet_id"] = request.subnet_id
|
|
205
|
+
result["subnet_node_id"] = request.subnet_node_id
|
|
206
|
+
|
|
207
|
+
return SimpleExtrinsicResponse(result)
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.error(f"Error activating subnet node: {e}")
|
|
211
|
+
return SimpleExtrinsicResponse({"success": False, "error": str(e)})
|
|
212
|
+
|
|
213
|
+
# ============================================================================
|
|
214
|
+
# SUBNET NODE PAUSE/REACTIVATE
|
|
215
|
+
# ============================================================================
|
|
216
|
+
|
|
217
|
+
def pause_subnet_node(
|
|
218
|
+
self, request: SubnetNodePauseRequest, keypair: Keypair
|
|
219
|
+
) -> dict[str, Any]:
|
|
220
|
+
"""
|
|
221
|
+
Pause a subnet node.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
request: Subnet node pause request
|
|
225
|
+
keypair: Keypair for signing the transaction
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Dictionary with operation result
|
|
229
|
+
"""
|
|
230
|
+
try:
|
|
231
|
+
logger.info(
|
|
232
|
+
f"Pausing subnet {request.subnet_id} node {request.subnet_node_id}"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
call = self.substrate.compose_call(
|
|
236
|
+
call_module="Network",
|
|
237
|
+
call_function="pause_subnet_node",
|
|
238
|
+
call_params={
|
|
239
|
+
"subnet_id": request.subnet_id,
|
|
240
|
+
"subnet_node_id": request.subnet_node_id,
|
|
241
|
+
},
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
response = self._submit_extrinsic(call, keypair)
|
|
245
|
+
|
|
246
|
+
if response["success"]:
|
|
247
|
+
response["message"] = (
|
|
248
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} paused successfully"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return response
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
logger.error(f"Error pausing subnet node: {e}")
|
|
255
|
+
return {"success": False, "error": str(e)}
|
|
256
|
+
|
|
257
|
+
def reactivate_subnet_node(
|
|
258
|
+
self, request: SubnetNodeReactivateRequest, keypair: Keypair
|
|
259
|
+
) -> dict[str, Any]:
|
|
260
|
+
"""
|
|
261
|
+
Reactivate a subnet node.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
request: Subnet node reactivation request
|
|
265
|
+
keypair: Keypair for signing the transaction
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Dictionary with operation result
|
|
269
|
+
"""
|
|
270
|
+
try:
|
|
271
|
+
logger.info(
|
|
272
|
+
f"Reactivating subnet {request.subnet_id} node {request.subnet_node_id}"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
call = self.substrate.compose_call(
|
|
276
|
+
call_module="Network",
|
|
277
|
+
call_function="reactivate_subnet_node",
|
|
278
|
+
call_params={
|
|
279
|
+
"subnet_id": request.subnet_id,
|
|
280
|
+
"subnet_node_id": request.subnet_node_id,
|
|
281
|
+
},
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
response = self._submit_extrinsic(call, keypair)
|
|
285
|
+
|
|
286
|
+
if response["success"]:
|
|
287
|
+
response["message"] = (
|
|
288
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} reactivated successfully"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return response
|
|
292
|
+
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logger.error(f"Error reactivating subnet node: {e}")
|
|
295
|
+
return {"success": False, "error": str(e)}
|
|
296
|
+
|
|
297
|
+
# ============================================================================
|
|
298
|
+
# SUBNET NODE REMOVAL
|
|
299
|
+
# ============================================================================
|
|
300
|
+
|
|
301
|
+
def remove_subnet_node(
|
|
302
|
+
self, request: SubnetNodeRemoveRequest, keypair: Keypair
|
|
303
|
+
) -> dict[str, Any]:
|
|
304
|
+
"""
|
|
305
|
+
Remove a subnet node.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
request: Subnet node removal request
|
|
309
|
+
keypair: Keypair for signing the transaction
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Dictionary with operation result
|
|
313
|
+
"""
|
|
314
|
+
try:
|
|
315
|
+
logger.info(
|
|
316
|
+
f"Removing subnet {request.subnet_id} node {request.subnet_node_id}"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
call = self.substrate.compose_call(
|
|
320
|
+
call_module="Network",
|
|
321
|
+
call_function="remove_subnet_node",
|
|
322
|
+
call_params={
|
|
323
|
+
"subnet_id": request.subnet_id,
|
|
324
|
+
"subnet_node_id": request.subnet_node_id,
|
|
325
|
+
},
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
response = self._submit_extrinsic(call, keypair)
|
|
329
|
+
|
|
330
|
+
if response["success"]:
|
|
331
|
+
response["message"] = (
|
|
332
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} removed successfully"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return response
|
|
336
|
+
|
|
337
|
+
except Exception as e:
|
|
338
|
+
logger.error(f"Error removing subnet node: {e}")
|
|
339
|
+
return {"success": False, "error": str(e)}
|
|
340
|
+
|
|
341
|
+
# ============================================================================
|
|
342
|
+
# SUBNET NODE PARAMETER UPDATES
|
|
343
|
+
# ============================================================================
|
|
344
|
+
|
|
345
|
+
def update_subnet_node_parameters(
|
|
346
|
+
self,
|
|
347
|
+
subnet_id: int,
|
|
348
|
+
subnet_node_id: int,
|
|
349
|
+
*,
|
|
350
|
+
peer_id: Optional[str] = None,
|
|
351
|
+
bootnode: Optional[str] = None,
|
|
352
|
+
bootnode_peer_id: Optional[str] = None,
|
|
353
|
+
client_peer_id: Optional[str] = None,
|
|
354
|
+
delegate_reward_rate: Optional[int] = None,
|
|
355
|
+
unique: Optional[str] = None,
|
|
356
|
+
non_unique: Optional[str] = None,
|
|
357
|
+
keypair: Keypair,
|
|
358
|
+
) -> list[dict[str, Any]]:
|
|
359
|
+
"""
|
|
360
|
+
Perform one or more subnet node parameter updates in sequence.
|
|
361
|
+
|
|
362
|
+
Returns a list of result dicts, each tagged with the logical field name.
|
|
363
|
+
"""
|
|
364
|
+
results: list[dict[str, Any]] = []
|
|
365
|
+
|
|
366
|
+
# Main peer ID
|
|
367
|
+
if peer_id is not None:
|
|
368
|
+
try:
|
|
369
|
+
logger.info(
|
|
370
|
+
f"Updating peer ID for subnet {subnet_id} node {subnet_node_id}"
|
|
371
|
+
)
|
|
372
|
+
request = SubnetNodePeerIdUpdateRequest(
|
|
373
|
+
subnet_id=subnet_id,
|
|
374
|
+
subnet_node_id=subnet_node_id,
|
|
375
|
+
new_peer_id=peer_id,
|
|
376
|
+
)
|
|
377
|
+
result = self.update_subnet_node_peer_id(request, keypair)
|
|
378
|
+
except Exception as e:
|
|
379
|
+
logger.error(f"Error updating subnet node peer ID: {e}")
|
|
380
|
+
result = {"success": False, "error": str(e)}
|
|
381
|
+
results.append({"field": "peer_id", "result": result})
|
|
382
|
+
|
|
383
|
+
# Bootnode multiaddress
|
|
384
|
+
if bootnode is not None:
|
|
385
|
+
try:
|
|
386
|
+
logger.info(
|
|
387
|
+
f"Updating bootnode for subnet {subnet_id} node {subnet_node_id}"
|
|
388
|
+
)
|
|
389
|
+
request = SubnetNodeBootnodeUpdateRequest(
|
|
390
|
+
subnet_id=subnet_id,
|
|
391
|
+
subnet_node_id=subnet_node_id,
|
|
392
|
+
new_bootnode=bootnode,
|
|
393
|
+
)
|
|
394
|
+
result = self.update_subnet_node_bootnode(request, keypair)
|
|
395
|
+
except Exception as e:
|
|
396
|
+
logger.error(f"Error updating subnet node bootnode: {e}")
|
|
397
|
+
result = {"success": False, "error": str(e)}
|
|
398
|
+
results.append({"field": "bootnode", "result": result})
|
|
399
|
+
|
|
400
|
+
# Bootnode peer ID
|
|
401
|
+
if bootnode_peer_id is not None:
|
|
402
|
+
try:
|
|
403
|
+
logger.info(
|
|
404
|
+
f"Updating bootnode peer ID for subnet {subnet_id} node {subnet_node_id}"
|
|
405
|
+
)
|
|
406
|
+
request = SubnetNodeBootnodePeerIdUpdateRequest(
|
|
407
|
+
subnet_id=subnet_id,
|
|
408
|
+
subnet_node_id=subnet_node_id,
|
|
409
|
+
new_bootnode_peer_id=bootnode_peer_id,
|
|
410
|
+
)
|
|
411
|
+
result = self.update_subnet_node_bootnode_peer_id(request, keypair)
|
|
412
|
+
except Exception as e:
|
|
413
|
+
logger.error(f"Error updating subnet node bootnode peer ID: {e}")
|
|
414
|
+
result = {"success": False, "error": str(e)}
|
|
415
|
+
results.append({"field": "bootnode_peer_id", "result": result})
|
|
416
|
+
|
|
417
|
+
# Client peer ID
|
|
418
|
+
if client_peer_id is not None:
|
|
419
|
+
try:
|
|
420
|
+
logger.info(
|
|
421
|
+
f"Updating client peer ID for subnet {subnet_id} node {subnet_node_id}"
|
|
422
|
+
)
|
|
423
|
+
request = SubnetNodeClientPeerIdUpdateRequest(
|
|
424
|
+
subnet_id=subnet_id,
|
|
425
|
+
subnet_node_id=subnet_node_id,
|
|
426
|
+
new_client_peer_id=client_peer_id,
|
|
427
|
+
)
|
|
428
|
+
result = self.update_subnet_node_client_peer_id(request, keypair)
|
|
429
|
+
except Exception as e:
|
|
430
|
+
logger.error(f"Error updating subnet node client peer ID: {e}")
|
|
431
|
+
result = {"success": False, "error": str(e)}
|
|
432
|
+
results.append({"field": "client_peer_id", "result": result})
|
|
433
|
+
|
|
434
|
+
# Delegate reward rate
|
|
435
|
+
if delegate_reward_rate is not None:
|
|
436
|
+
try:
|
|
437
|
+
logger.info(
|
|
438
|
+
f"Updating delegate reward rate for subnet {subnet_id} node {subnet_node_id}"
|
|
439
|
+
)
|
|
440
|
+
request = SubnetNodeUpdateRequest(
|
|
441
|
+
subnet_id=subnet_id,
|
|
442
|
+
subnet_node_id=subnet_node_id,
|
|
443
|
+
value=str(delegate_reward_rate),
|
|
444
|
+
)
|
|
445
|
+
result = self.update_subnet_node_delegate_reward_rate(request, keypair)
|
|
446
|
+
except Exception as e:
|
|
447
|
+
logger.error(f"Error updating subnet node delegate reward rate: {e}")
|
|
448
|
+
result = {"success": False, "error": str(e)}
|
|
449
|
+
results.append({"field": "delegate_reward_rate", "result": result})
|
|
450
|
+
|
|
451
|
+
# Unique metadata
|
|
452
|
+
if unique is not None:
|
|
453
|
+
try:
|
|
454
|
+
logger.info(
|
|
455
|
+
f"Updating unique metadata for subnet {subnet_id} node {subnet_node_id}"
|
|
456
|
+
)
|
|
457
|
+
request = SubnetNodeUpdateRequest(
|
|
458
|
+
subnet_id=subnet_id,
|
|
459
|
+
subnet_node_id=subnet_node_id,
|
|
460
|
+
value=unique,
|
|
461
|
+
)
|
|
462
|
+
result = self.update_subnet_node_unique(request, keypair)
|
|
463
|
+
except Exception as e:
|
|
464
|
+
logger.error(f"Error updating subnet node unique metadata: {e}")
|
|
465
|
+
result = {"success": False, "error": str(e)}
|
|
466
|
+
results.append({"field": "unique", "result": result})
|
|
467
|
+
|
|
468
|
+
# Non-unique metadata
|
|
469
|
+
if non_unique is not None:
|
|
470
|
+
try:
|
|
471
|
+
logger.info(
|
|
472
|
+
f"Updating non-unique metadata for subnet {subnet_id} node {subnet_node_id}"
|
|
473
|
+
)
|
|
474
|
+
request = SubnetNodeUpdateRequest(
|
|
475
|
+
subnet_id=subnet_id,
|
|
476
|
+
subnet_node_id=subnet_node_id,
|
|
477
|
+
value=non_unique,
|
|
478
|
+
)
|
|
479
|
+
result = self.update_subnet_node_non_unique(request, keypair)
|
|
480
|
+
except Exception as e:
|
|
481
|
+
logger.error(f"Error updating subnet node non-unique metadata: {e}")
|
|
482
|
+
result = {"success": False, "error": str(e)}
|
|
483
|
+
results.append({"field": "non_unique", "result": result})
|
|
484
|
+
|
|
485
|
+
return results
|
|
486
|
+
|
|
487
|
+
def update_subnet_node_unique(
|
|
488
|
+
self, request: SubnetNodeUpdateRequest, keypair: Keypair
|
|
489
|
+
) -> dict[str, Any]:
|
|
490
|
+
"""
|
|
491
|
+
Update subnet node unique parameter.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
request: Subnet node update request
|
|
495
|
+
keypair: Keypair for signing the transaction
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
Dictionary with operation result
|
|
499
|
+
"""
|
|
500
|
+
try:
|
|
501
|
+
logger.info(
|
|
502
|
+
f"Updating subnet {request.subnet_id} node {request.subnet_node_id} unique parameter"
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
call = self.substrate.compose_call(
|
|
506
|
+
call_module="Network",
|
|
507
|
+
call_function="update_subnet_node_unique",
|
|
508
|
+
call_params={
|
|
509
|
+
"subnet_id": request.subnet_id,
|
|
510
|
+
"subnet_node_id": request.subnet_node_id,
|
|
511
|
+
"value": request.value.encode("utf-8") if request.value else None,
|
|
512
|
+
},
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
response = self._submit_extrinsic(call, keypair)
|
|
516
|
+
|
|
517
|
+
if response["success"]:
|
|
518
|
+
response["message"] = (
|
|
519
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} unique parameter updated successfully"
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
return response
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
logger.error(f"Error updating subnet node unique parameter: {e}")
|
|
526
|
+
return {"success": False, "error": str(e)}
|
|
527
|
+
|
|
528
|
+
def update_subnet_node_non_unique(
|
|
529
|
+
self, request: SubnetNodeUpdateRequest, keypair: Keypair
|
|
530
|
+
) -> dict[str, Any]:
|
|
531
|
+
"""
|
|
532
|
+
Update subnet node non-unique parameter.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
request: Subnet node update request
|
|
536
|
+
keypair: Keypair for signing the transaction
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Dictionary with operation result
|
|
540
|
+
"""
|
|
541
|
+
try:
|
|
542
|
+
logger.info(
|
|
543
|
+
f"Updating subnet {request.subnet_id} node {request.subnet_node_id} non-unique parameter"
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
call = self.substrate.compose_call(
|
|
547
|
+
call_module="Network",
|
|
548
|
+
call_function="update_subnet_node_non_unique",
|
|
549
|
+
call_params={
|
|
550
|
+
"subnet_id": request.subnet_id,
|
|
551
|
+
"subnet_node_id": request.subnet_node_id,
|
|
552
|
+
"value": request.value.encode("utf-8") if request.value else None,
|
|
553
|
+
},
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
response = self._submit_extrinsic(call, keypair)
|
|
557
|
+
|
|
558
|
+
if response["success"]:
|
|
559
|
+
response["message"] = (
|
|
560
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} non-unique parameter updated successfully"
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
return response
|
|
564
|
+
|
|
565
|
+
except Exception as e:
|
|
566
|
+
logger.error(f"Error updating subnet node non-unique parameter: {e}")
|
|
567
|
+
return {"success": False, "error": str(e)}
|
|
568
|
+
|
|
569
|
+
def update_subnet_node_peer_id(
|
|
570
|
+
self, request: SubnetNodePeerIdUpdateRequest, keypair: Keypair
|
|
571
|
+
) -> dict[str, Any]:
|
|
572
|
+
"""
|
|
573
|
+
Update subnet node peer ID.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
request: Subnet node peer ID update request
|
|
577
|
+
keypair: Keypair for signing the transaction
|
|
578
|
+
|
|
579
|
+
Returns:
|
|
580
|
+
Dictionary with operation result
|
|
581
|
+
"""
|
|
582
|
+
try:
|
|
583
|
+
logger.info(
|
|
584
|
+
f"Updating subnet {request.subnet_id} node {request.subnet_node_id} peer ID"
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
# peer_id passed as string, substrate-interface handles encoding
|
|
588
|
+
call = self.substrate.compose_call(
|
|
589
|
+
call_module="Network",
|
|
590
|
+
call_function="update_subnet_node_peer_id",
|
|
591
|
+
call_params={
|
|
592
|
+
"subnet_id": request.subnet_id,
|
|
593
|
+
"subnet_node_id": request.subnet_node_id,
|
|
594
|
+
"new_peer_id": request.new_peer_id, # String, not encoded
|
|
595
|
+
},
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
response = self._submit_extrinsic(call, keypair)
|
|
599
|
+
|
|
600
|
+
if response["success"]:
|
|
601
|
+
response["message"] = (
|
|
602
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} peer ID updated successfully"
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
return response
|
|
606
|
+
|
|
607
|
+
except Exception as e:
|
|
608
|
+
logger.error(f"Error updating subnet node peer ID: {e}")
|
|
609
|
+
return {"success": False, "error": str(e)}
|
|
610
|
+
|
|
611
|
+
def update_subnet_node_bootnode(
|
|
612
|
+
self, request: SubnetNodeBootnodeUpdateRequest, keypair: Keypair
|
|
613
|
+
) -> dict[str, Any]:
|
|
614
|
+
"""
|
|
615
|
+
Update subnet node bootnode.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
request: Subnet node bootnode update request
|
|
619
|
+
keypair: Keypair for signing the transaction
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
Dictionary with operation result
|
|
623
|
+
"""
|
|
624
|
+
try:
|
|
625
|
+
logger.info(
|
|
626
|
+
f"Updating subnet {request.subnet_id} node {request.subnet_node_id} bootnode"
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
call = self.substrate.compose_call(
|
|
630
|
+
call_module="Network",
|
|
631
|
+
call_function="update_subnet_node_bootnode",
|
|
632
|
+
call_params={
|
|
633
|
+
"subnet_id": request.subnet_id,
|
|
634
|
+
"subnet_node_id": request.subnet_node_id,
|
|
635
|
+
"new_bootnode": (
|
|
636
|
+
request.new_bootnode.encode("utf-8")
|
|
637
|
+
if request.new_bootnode
|
|
638
|
+
else None
|
|
639
|
+
),
|
|
640
|
+
},
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
response = self._submit_extrinsic(call, keypair)
|
|
644
|
+
|
|
645
|
+
if response["success"]:
|
|
646
|
+
response["message"] = (
|
|
647
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} bootnode updated successfully"
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
return response
|
|
651
|
+
|
|
652
|
+
except Exception as e:
|
|
653
|
+
logger.error(f"Error updating subnet node bootnode: {e}")
|
|
654
|
+
return {"success": False, "error": str(e)}
|
|
655
|
+
|
|
656
|
+
def update_subnet_node_bootnode_peer_id(
|
|
657
|
+
self, request: SubnetNodeBootnodePeerIdUpdateRequest, keypair: Keypair
|
|
658
|
+
) -> dict[str, Any]:
|
|
659
|
+
"""
|
|
660
|
+
Update subnet node bootnode peer ID.
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
request: Subnet node bootnode peer ID update request
|
|
664
|
+
keypair: Keypair for signing the transaction
|
|
665
|
+
|
|
666
|
+
Returns:
|
|
667
|
+
Dictionary with operation result
|
|
668
|
+
"""
|
|
669
|
+
try:
|
|
670
|
+
logger.info(
|
|
671
|
+
f"Updating subnet {request.subnet_id} node {request.subnet_node_id} bootnode peer ID"
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
# peer_id passed as string, substrate-interface handles encoding
|
|
675
|
+
call = self.substrate.compose_call(
|
|
676
|
+
call_module="Network",
|
|
677
|
+
call_function="update_bootnode_peer_id",
|
|
678
|
+
call_params={
|
|
679
|
+
"subnet_id": request.subnet_id,
|
|
680
|
+
"subnet_node_id": request.subnet_node_id,
|
|
681
|
+
"new_bootnode_peer_id": request.new_bootnode_peer_id, # String, not encoded
|
|
682
|
+
},
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
response = self._submit_extrinsic(call, keypair)
|
|
686
|
+
|
|
687
|
+
if response["success"]:
|
|
688
|
+
response["message"] = (
|
|
689
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} bootnode peer ID updated successfully"
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
return response
|
|
693
|
+
|
|
694
|
+
except Exception as e:
|
|
695
|
+
logger.error(f"Error updating subnet node bootnode peer ID: {e}")
|
|
696
|
+
return {"success": False, "error": str(e)}
|
|
697
|
+
|
|
698
|
+
def update_subnet_node_client_peer_id(
|
|
699
|
+
self, request: SubnetNodeClientPeerIdUpdateRequest, keypair: Keypair
|
|
700
|
+
) -> dict[str, Any]:
|
|
701
|
+
"""
|
|
702
|
+
Update subnet node client peer ID.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
request: Subnet node client peer ID update request
|
|
706
|
+
keypair: Keypair for signing the transaction
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
Dictionary with operation result
|
|
710
|
+
"""
|
|
711
|
+
try:
|
|
712
|
+
logger.info(
|
|
713
|
+
f"Updating subnet {request.subnet_id} node {request.subnet_node_id} client peer ID"
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# peer_id passed as string, substrate-interface handles encoding
|
|
717
|
+
call = self.substrate.compose_call(
|
|
718
|
+
call_module="Network",
|
|
719
|
+
call_function="update_client_peer_id",
|
|
720
|
+
call_params={
|
|
721
|
+
"subnet_id": request.subnet_id,
|
|
722
|
+
"subnet_node_id": request.subnet_node_id,
|
|
723
|
+
"new_client_peer_id": request.new_client_peer_id, # String, not encoded
|
|
724
|
+
},
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
response = self._submit_extrinsic(call, keypair)
|
|
728
|
+
|
|
729
|
+
if response["success"]:
|
|
730
|
+
response["message"] = (
|
|
731
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} client peer ID updated successfully"
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
return response
|
|
735
|
+
|
|
736
|
+
except Exception as e:
|
|
737
|
+
logger.error(f"Error updating subnet node client peer ID: {e}")
|
|
738
|
+
return {"success": False, "error": str(e)}
|
|
739
|
+
|
|
740
|
+
def update_subnet_node_delegate_reward_rate(
|
|
741
|
+
self, request: SubnetNodeUpdateRequest, keypair: Keypair
|
|
742
|
+
) -> dict[str, Any]:
|
|
743
|
+
"""
|
|
744
|
+
Update subnet node delegate reward rate.
|
|
745
|
+
|
|
746
|
+
Args:
|
|
747
|
+
request: Subnet node update request (value should be the new reward rate)
|
|
748
|
+
keypair: Keypair for signing the transaction
|
|
749
|
+
|
|
750
|
+
Returns:
|
|
751
|
+
Dictionary with operation result
|
|
752
|
+
"""
|
|
753
|
+
try:
|
|
754
|
+
logger.info(
|
|
755
|
+
f"Updating subnet {request.subnet_id} node {request.subnet_node_id} delegate reward rate"
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
# Convert string value to int for reward rate
|
|
759
|
+
reward_rate = int(request.value) if request.value else 0
|
|
760
|
+
|
|
761
|
+
call = self.substrate.compose_call(
|
|
762
|
+
call_module="Network",
|
|
763
|
+
call_function="update_subnet_node_delegate_reward_rate",
|
|
764
|
+
call_params={
|
|
765
|
+
"subnet_id": request.subnet_id,
|
|
766
|
+
"subnet_node_id": request.subnet_node_id,
|
|
767
|
+
"value": reward_rate,
|
|
768
|
+
},
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
response = self._submit_extrinsic(call, keypair)
|
|
772
|
+
|
|
773
|
+
if response["success"]:
|
|
774
|
+
response["message"] = (
|
|
775
|
+
f"Subnet {request.subnet_id} node {request.subnet_node_id} delegate reward rate updated successfully"
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
return response
|
|
779
|
+
|
|
780
|
+
except Exception as e:
|
|
781
|
+
logger.error(f"Error updating subnet node delegate reward rate: {e}")
|
|
782
|
+
return {"success": False, "error": str(e)}
|
|
783
|
+
|
|
784
|
+
# ============================================================================
|
|
785
|
+
# QUERY METHODS
|
|
786
|
+
# ============================================================================
|
|
787
|
+
|
|
788
|
+
def get_subnet_node_info(
|
|
789
|
+
self, subnet_id: int, subnet_node_id: int
|
|
790
|
+
) -> Optional[SubnetNodeInfo]:
|
|
791
|
+
"""
|
|
792
|
+
Get information about a specific subnet node.
|
|
793
|
+
|
|
794
|
+
Args:
|
|
795
|
+
subnet_id: The subnet ID
|
|
796
|
+
subnet_node_id: The subnet node ID
|
|
797
|
+
|
|
798
|
+
Returns:
|
|
799
|
+
SubnetNodeInfo if found, None otherwise
|
|
800
|
+
"""
|
|
801
|
+
try:
|
|
802
|
+
# Query subnet node data
|
|
803
|
+
node_data = self.substrate.query(
|
|
804
|
+
module="Network",
|
|
805
|
+
storage_function="SubnetNodesData",
|
|
806
|
+
params=[subnet_id, subnet_node_id],
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
if not node_data.value:
|
|
810
|
+
return None
|
|
811
|
+
|
|
812
|
+
# Query additional information
|
|
813
|
+
stake_balance = (
|
|
814
|
+
self.substrate.query(
|
|
815
|
+
module="Network",
|
|
816
|
+
storage_function="SubnetNodeStake",
|
|
817
|
+
params=[subnet_id, subnet_node_id],
|
|
818
|
+
).value
|
|
819
|
+
or 0
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
node_delegate_stake_balance = (
|
|
823
|
+
self.substrate.query(
|
|
824
|
+
module="Network",
|
|
825
|
+
storage_function="SubnetNodeDelegateStake",
|
|
826
|
+
params=[subnet_id, subnet_node_id],
|
|
827
|
+
).value
|
|
828
|
+
or 0
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
penalties = (
|
|
832
|
+
self.substrate.query(
|
|
833
|
+
module="Network",
|
|
834
|
+
storage_function="SubnetNodePenalties",
|
|
835
|
+
params=[subnet_id, subnet_node_id],
|
|
836
|
+
).value
|
|
837
|
+
or 0
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
# Query coldkey
|
|
841
|
+
coldkey = self.substrate.query(
|
|
842
|
+
module="Network",
|
|
843
|
+
storage_function="SubnetNodeColdkey",
|
|
844
|
+
params=[subnet_id, subnet_node_id],
|
|
845
|
+
).value
|
|
846
|
+
|
|
847
|
+
# Query identity data
|
|
848
|
+
identity = self.substrate.query(
|
|
849
|
+
module="Network", storage_function="Identity", params=[coldkey]
|
|
850
|
+
).value
|
|
851
|
+
|
|
852
|
+
# Query reputation
|
|
853
|
+
reputation = self.substrate.query(
|
|
854
|
+
module="Network", storage_function="Reputation", params=[coldkey]
|
|
855
|
+
).value
|
|
856
|
+
|
|
857
|
+
return SubnetNodeInfo(
|
|
858
|
+
subnet_id=subnet_id,
|
|
859
|
+
subnet_node_id=subnet_node_id,
|
|
860
|
+
coldkey=coldkey,
|
|
861
|
+
hotkey=node_data.value["hotkey"],
|
|
862
|
+
peer_id=node_data.value["peer_id"].decode("utf-8"),
|
|
863
|
+
bootnode_peer_id=node_data.value["bootnode_peer_id"].decode("utf-8"),
|
|
864
|
+
client_peer_id=node_data.value["client_peer_id"].decode("utf-8"),
|
|
865
|
+
bootnode=(
|
|
866
|
+
node_data.value.get("bootnode", {}).decode("utf-8")
|
|
867
|
+
if node_data.value.get("bootnode")
|
|
868
|
+
else None
|
|
869
|
+
),
|
|
870
|
+
identity=identity,
|
|
871
|
+
classification=node_data.value["classification"].name,
|
|
872
|
+
delegate_reward_rate=node_data.value.get("delegate_reward_rate", 0),
|
|
873
|
+
last_delegate_reward_rate_update=node_data.value.get(
|
|
874
|
+
"last_delegate_reward_rate_update", 0
|
|
875
|
+
),
|
|
876
|
+
unique=(
|
|
877
|
+
node_data.value.get("unique", {}).decode("utf-8")
|
|
878
|
+
if node_data.value.get("unique")
|
|
879
|
+
else None
|
|
880
|
+
),
|
|
881
|
+
non_unique=(
|
|
882
|
+
node_data.value.get("non_unique", {}).decode("utf-8")
|
|
883
|
+
if node_data.value.get("non_unique")
|
|
884
|
+
else None
|
|
885
|
+
),
|
|
886
|
+
stake_balance=stake_balance,
|
|
887
|
+
node_delegate_stake_balance=node_delegate_stake_balance,
|
|
888
|
+
penalties=penalties,
|
|
889
|
+
reputation=reputation,
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
except Exception as e:
|
|
893
|
+
logger.error(f"Error getting subnet node info: {e}")
|
|
894
|
+
return None
|
|
895
|
+
|
|
896
|
+
def list_subnet_nodes(self, subnet_id: int) -> list[SubnetNodeInfo]:
|
|
897
|
+
"""
|
|
898
|
+
List all nodes for a specific subnet.
|
|
899
|
+
|
|
900
|
+
Args:
|
|
901
|
+
subnet_id: The subnet ID
|
|
902
|
+
|
|
903
|
+
Returns:
|
|
904
|
+
List of SubnetNodeInfo objects
|
|
905
|
+
"""
|
|
906
|
+
try:
|
|
907
|
+
nodes = []
|
|
908
|
+
|
|
909
|
+
# Get total node count for this subnet
|
|
910
|
+
total_nodes = (
|
|
911
|
+
self.substrate.query(
|
|
912
|
+
module="Network",
|
|
913
|
+
storage_function="TotalSubnetNodes",
|
|
914
|
+
params=[subnet_id],
|
|
915
|
+
).value
|
|
916
|
+
or 0
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
# Query each node
|
|
920
|
+
for node_id in range(1, total_nodes + 1):
|
|
921
|
+
node_info = self.get_subnet_node_info(subnet_id, node_id)
|
|
922
|
+
if node_info:
|
|
923
|
+
nodes.append(node_info)
|
|
924
|
+
|
|
925
|
+
return nodes
|
|
926
|
+
|
|
927
|
+
except Exception as e:
|
|
928
|
+
logger.error(f"Error listing subnet nodes: {e}")
|
|
929
|
+
return []
|
|
930
|
+
|
|
931
|
+
# ============================================================================
|
|
932
|
+
# PRIVATE HELPER METHODS
|
|
933
|
+
# ============================================================================
|
|
934
|
+
|
|
935
|
+
def _validate_node_registration_request(self, request: SubnetNodeRegisterRequest):
|
|
936
|
+
"""Validate the node registration request."""
|
|
937
|
+
if request.validator_id < 0:
|
|
938
|
+
raise ValueError("Validator ID must be non-negative")
|
|
939
|
+
|
|
940
|
+
# Validate account IDs
|
|
941
|
+
validate_ethereum_address(request.hotkey)
|
|
942
|
+
|
|
943
|
+
# Validate peer IDs
|
|
944
|
+
if not request.peer_id or not request.peer_id.strip():
|
|
945
|
+
raise ValueError("Peer ID cannot be empty")
|
|
946
|
+
if not request.bootnode_peer_id or not request.bootnode_peer_id.strip():
|
|
947
|
+
raise ValueError("Bootnode peer ID cannot be empty")
|
|
948
|
+
if not request.client_peer_id or not request.client_peer_id.strip():
|
|
949
|
+
raise ValueError("Client peer ID cannot be empty")
|
|
950
|
+
|
|
951
|
+
# Validate stake amount
|
|
952
|
+
if request.stake_to_be_added <= 0:
|
|
953
|
+
raise ValueError("Stake amount must be positive")
|
|
954
|
+
|
|
955
|
+
def _submit_extrinsic(self, call, keypair: Keypair) -> dict[str, Any]:
|
|
956
|
+
"""Submit an extrinsic and return the result."""
|
|
957
|
+
try:
|
|
958
|
+
extrinsic = self.substrate.create_signed_extrinsic(
|
|
959
|
+
call=call,
|
|
960
|
+
keypair=keypair,
|
|
961
|
+
era={"period": 64}, # 64 block era
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
response = self.substrate.submit_extrinsic(
|
|
965
|
+
extrinsic, wait_for_inclusion=True, wait_for_finalization=True
|
|
966
|
+
)
|
|
967
|
+
|
|
968
|
+
if response.is_success:
|
|
969
|
+
# Extract extrinsic_hash with defensive handling
|
|
970
|
+
try:
|
|
971
|
+
extrinsic_hash = response.extrinsic_hash
|
|
972
|
+
if isinstance(extrinsic_hash, bytes):
|
|
973
|
+
extrinsic_hash = "0x" + extrinsic_hash.hex()
|
|
974
|
+
elif extrinsic_hash and not isinstance(extrinsic_hash, str):
|
|
975
|
+
extrinsic_hash = str(extrinsic_hash)
|
|
976
|
+
except (AttributeError, Exception):
|
|
977
|
+
extrinsic_hash = getattr(response, "extrinsic_hash", None)
|
|
978
|
+
if extrinsic_hash and isinstance(extrinsic_hash, bytes):
|
|
979
|
+
extrinsic_hash = "0x" + extrinsic_hash.hex()
|
|
980
|
+
|
|
981
|
+
# Extract block_hash with defensive handling
|
|
982
|
+
try:
|
|
983
|
+
block_hash = response.block_hash
|
|
984
|
+
if isinstance(block_hash, bytes):
|
|
985
|
+
block_hash = "0x" + block_hash.hex()
|
|
986
|
+
elif block_hash and not isinstance(block_hash, str):
|
|
987
|
+
block_hash = str(block_hash)
|
|
988
|
+
except (AttributeError, Exception):
|
|
989
|
+
block_hash = getattr(response, "block_hash", None)
|
|
990
|
+
if block_hash and isinstance(block_hash, bytes):
|
|
991
|
+
block_hash = "0x" + block_hash.hex()
|
|
992
|
+
|
|
993
|
+
# Extract block_number with defensive handling
|
|
994
|
+
try:
|
|
995
|
+
block_number = response.block_number
|
|
996
|
+
except (AttributeError, Exception):
|
|
997
|
+
block_number = getattr(response, "block_number", None)
|
|
998
|
+
|
|
999
|
+
# If block_number is not available but we have block_hash, query it
|
|
1000
|
+
if block_number is None and block_hash:
|
|
1001
|
+
try:
|
|
1002
|
+
block_number = self.substrate.get_block_number(
|
|
1003
|
+
block_hash=block_hash
|
|
1004
|
+
)
|
|
1005
|
+
except Exception as e:
|
|
1006
|
+
logger.debug(f"Could not get block number from block hash: {e}")
|
|
1007
|
+
block_number = None
|
|
1008
|
+
|
|
1009
|
+
# Extract events
|
|
1010
|
+
try:
|
|
1011
|
+
events = response.triggered_events
|
|
1012
|
+
except (AttributeError, Exception):
|
|
1013
|
+
events = getattr(response, "triggered_events", []) or getattr(
|
|
1014
|
+
response, "events", []
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
return {
|
|
1018
|
+
"success": True,
|
|
1019
|
+
"extrinsic_hash": extrinsic_hash,
|
|
1020
|
+
"transaction_hash": extrinsic_hash, # Alias for compatibility
|
|
1021
|
+
"block_hash": block_hash,
|
|
1022
|
+
"block_number": block_number,
|
|
1023
|
+
"events": events,
|
|
1024
|
+
}
|
|
1025
|
+
else:
|
|
1026
|
+
error_msg = self._extract_error_message(response)
|
|
1027
|
+
return {"success": False, "error": error_msg}
|
|
1028
|
+
|
|
1029
|
+
except Exception as e:
|
|
1030
|
+
logger.error(f"Error submitting extrinsic: {e}")
|
|
1031
|
+
return {"success": False, "error": str(e)}
|
|
1032
|
+
|
|
1033
|
+
def _extract_error_message(self, response) -> str:
|
|
1034
|
+
"""Extract error message from failed response."""
|
|
1035
|
+
try:
|
|
1036
|
+
if response.error_message:
|
|
1037
|
+
error_msg = response.error_message
|
|
1038
|
+
if isinstance(error_msg, dict):
|
|
1039
|
+
return error_msg.get("name", str(error_msg))
|
|
1040
|
+
return str(error_msg)
|
|
1041
|
+
|
|
1042
|
+
for event in response.triggered_events:
|
|
1043
|
+
if (
|
|
1044
|
+
event.value["module_id"] == "System"
|
|
1045
|
+
and event.value["event_id"] == "ExtrinsicFailed"
|
|
1046
|
+
):
|
|
1047
|
+
error = event.value["attributes"][0]
|
|
1048
|
+
if hasattr(error, "name"):
|
|
1049
|
+
return error.name
|
|
1050
|
+
return str(error)
|
|
1051
|
+
except Exception as e:
|
|
1052
|
+
logger.warning(f"Could not extract error message: {e}")
|
|
1053
|
+
|
|
1054
|
+
return "Unknown error occurred"
|