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,844 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Node command handlers for RPC-based operations and extrinsic operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from ...dependencies import get_client
|
|
8
|
+
from ...errors.handlers import handle_and_display_error, handle_and_display_node_error
|
|
9
|
+
from ...ui.components import HTCLILoadingContext
|
|
10
|
+
from ...utils import retrieve_wallet_with_validation
|
|
11
|
+
from .display import (
|
|
12
|
+
display_all_nodes_rpc,
|
|
13
|
+
display_node_info_rpc,
|
|
14
|
+
display_node_lifecycle_result,
|
|
15
|
+
display_node_register_result,
|
|
16
|
+
display_node_remove_result,
|
|
17
|
+
display_node_update_summary,
|
|
18
|
+
display_overwatch_commits_rpc,
|
|
19
|
+
display_overwatch_reveals_rpc,
|
|
20
|
+
)
|
|
21
|
+
from .error_handling import handle_node_error
|
|
22
|
+
from .prompts import (
|
|
23
|
+
normalize_delegate_reward_rate,
|
|
24
|
+
prompt_node_peer_id_update,
|
|
25
|
+
prompt_node_register,
|
|
26
|
+
prompt_node_remove,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_all_nodes_handler():
|
|
31
|
+
"""Handle getting all nodes using RPC."""
|
|
32
|
+
try:
|
|
33
|
+
client = get_client()
|
|
34
|
+
|
|
35
|
+
# Check if client is connected
|
|
36
|
+
if not client.substrate:
|
|
37
|
+
from ...ui.display import print_error
|
|
38
|
+
print_error("Not connected to blockchain. Please check your network configuration.")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
with HTCLILoadingContext("Fetching all nodes across all subnets..."):
|
|
42
|
+
result = client.rpc.node.get_all_subnet_nodes_info()
|
|
43
|
+
|
|
44
|
+
# Result can be an empty list (legitimate - no nodes) or None (error)
|
|
45
|
+
# The RPC method returns [] on error or when no nodes exist
|
|
46
|
+
if result is not None:
|
|
47
|
+
if len(result) == 0:
|
|
48
|
+
from ...ui.display import print_info
|
|
49
|
+
print_info("No nodes found across all subnets")
|
|
50
|
+
else:
|
|
51
|
+
display_all_nodes_rpc(result)
|
|
52
|
+
else:
|
|
53
|
+
from ...ui.display import print_error
|
|
54
|
+
print_error("Failed to retrieve node information from the blockchain")
|
|
55
|
+
|
|
56
|
+
except RuntimeError as e:
|
|
57
|
+
handle_and_display_error(e, operation="list")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
error_msg = str(e)
|
|
60
|
+
if not handle_node_error(error_msg, None, None, "list", client if 'client' in locals() else None):
|
|
61
|
+
handle_and_display_node_error(e, operation="list")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_node_info_handler(subnet_id: Optional[int], node_id: Optional[int]):
|
|
65
|
+
"""Handle getting specific node information using RPC."""
|
|
66
|
+
try:
|
|
67
|
+
if subnet_id is None:
|
|
68
|
+
from ...utils.prompts import integer_prompt
|
|
69
|
+
|
|
70
|
+
subnet_id = integer_prompt("Enter subnet ID")
|
|
71
|
+
|
|
72
|
+
if node_id is None:
|
|
73
|
+
from ...utils.prompts import integer_prompt
|
|
74
|
+
|
|
75
|
+
node_id = integer_prompt("Enter node ID")
|
|
76
|
+
|
|
77
|
+
client = get_client()
|
|
78
|
+
|
|
79
|
+
with HTCLILoadingContext(f"Fetching node {node_id} in subnet {subnet_id}..."):
|
|
80
|
+
result = client.rpc.node.get_subnet_node_info(subnet_id, node_id)
|
|
81
|
+
|
|
82
|
+
if result:
|
|
83
|
+
display_node_info_rpc(result, subnet_id, node_id)
|
|
84
|
+
else:
|
|
85
|
+
from ...ui.display import print_info
|
|
86
|
+
|
|
87
|
+
print_info(f"Node {node_id} not found in subnet {subnet_id}")
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
error_msg = str(e)
|
|
91
|
+
if not handle_node_error(error_msg, subnet_id, node_id, "info", client if 'client' in locals() else None):
|
|
92
|
+
handle_and_display_node_error(e, operation="info")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_overwatch_commits_handler(
|
|
96
|
+
epoch: Optional[int], overwatch_node_id: Optional[int]
|
|
97
|
+
):
|
|
98
|
+
"""Handle getting overwatch commits using RPC."""
|
|
99
|
+
try:
|
|
100
|
+
if epoch is None:
|
|
101
|
+
from ...utils.prompts import integer_prompt
|
|
102
|
+
|
|
103
|
+
epoch = integer_prompt("Enter epoch number")
|
|
104
|
+
|
|
105
|
+
if overwatch_node_id is None:
|
|
106
|
+
from ...utils.prompts import integer_prompt
|
|
107
|
+
|
|
108
|
+
overwatch_node_id = integer_prompt("Enter overwatch node ID")
|
|
109
|
+
|
|
110
|
+
client = get_client()
|
|
111
|
+
|
|
112
|
+
with HTCLILoadingContext(
|
|
113
|
+
f"Fetching overwatch commits for epoch {epoch}, node {overwatch_node_id}..."
|
|
114
|
+
):
|
|
115
|
+
result = client.rpc.overwatch.get_overwatch_commits_for_epoch_and_node(
|
|
116
|
+
epoch, overwatch_node_id
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if result:
|
|
120
|
+
display_overwatch_commits_rpc(result, epoch, overwatch_node_id)
|
|
121
|
+
else:
|
|
122
|
+
from ...ui.display import print_error
|
|
123
|
+
|
|
124
|
+
print_error(f"Failed to get overwatch commits for epoch {epoch}, node {overwatch_node_id}")
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
error_msg = str(e)
|
|
128
|
+
if not handle_node_error(error_msg, None, overwatch_node_id, "overwatch-commits", client if 'client' in locals() else None):
|
|
129
|
+
handle_and_display_node_error(e, operation="overwatch-commits")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_overwatch_reveals_handler(
|
|
133
|
+
epoch: Optional[int], overwatch_node_id: Optional[int]
|
|
134
|
+
):
|
|
135
|
+
"""Handle getting overwatch reveals using RPC."""
|
|
136
|
+
try:
|
|
137
|
+
if epoch is None:
|
|
138
|
+
from ...utils.prompts import integer_prompt
|
|
139
|
+
|
|
140
|
+
epoch = integer_prompt("Enter epoch number")
|
|
141
|
+
|
|
142
|
+
if overwatch_node_id is None:
|
|
143
|
+
from ...utils.prompts import integer_prompt
|
|
144
|
+
|
|
145
|
+
overwatch_node_id = integer_prompt("Enter overwatch node ID")
|
|
146
|
+
|
|
147
|
+
client = get_client()
|
|
148
|
+
|
|
149
|
+
with HTCLILoadingContext(
|
|
150
|
+
f"Fetching overwatch reveals for epoch {epoch}, node {overwatch_node_id}..."
|
|
151
|
+
):
|
|
152
|
+
result = client.rpc.overwatch.get_overwatch_reveals_for_epoch_and_node(
|
|
153
|
+
epoch, overwatch_node_id
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if result:
|
|
157
|
+
display_overwatch_reveals_rpc(result, epoch, overwatch_node_id)
|
|
158
|
+
else:
|
|
159
|
+
from ...ui.display import print_error
|
|
160
|
+
|
|
161
|
+
print_error(f"Failed to get overwatch reveals for epoch {epoch}, node {overwatch_node_id}")
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
error_msg = str(e)
|
|
165
|
+
if not handle_node_error(error_msg, None, overwatch_node_id, "overwatch-reveals", client if 'client' in locals() else None):
|
|
166
|
+
handle_and_display_node_error(e, operation="overwatch-reveals")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ============================================================================
|
|
170
|
+
# EXTRINSIC HANDLERS - Write Operations
|
|
171
|
+
# ============================================================================
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def register_node_handler(
|
|
175
|
+
subnet_id: Optional[int] = None,
|
|
176
|
+
validator_id: Optional[int] = None,
|
|
177
|
+
hotkey: Optional[str] = None,
|
|
178
|
+
stake_amount: Optional[float] = None,
|
|
179
|
+
peer_id: Optional[str] = None,
|
|
180
|
+
bootnode_peer_id: Optional[str] = None,
|
|
181
|
+
client_peer_id: Optional[str] = None,
|
|
182
|
+
coldkey: Optional[str] = None,
|
|
183
|
+
):
|
|
184
|
+
"""Handle node registration on a subnet."""
|
|
185
|
+
try:
|
|
186
|
+
from ...ui.display import print_error
|
|
187
|
+
from ...utils.validation import validate_stake_amount_prompt
|
|
188
|
+
|
|
189
|
+
def validate_cli_peer_id(label: str, value: Optional[str]) -> None:
|
|
190
|
+
if value is None:
|
|
191
|
+
return
|
|
192
|
+
if not value or not value.isalnum():
|
|
193
|
+
raise ValueError(f"Invalid {label}: must contain only letters and numbers")
|
|
194
|
+
|
|
195
|
+
if stake_amount is not None:
|
|
196
|
+
is_valid, error_msg = validate_stake_amount_prompt(stake_amount)
|
|
197
|
+
if not is_valid:
|
|
198
|
+
print_error(f"Invalid stake amount: {error_msg}")
|
|
199
|
+
raise ValueError(f"Invalid stake amount: {error_msg}")
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
validate_cli_peer_id("peer_id", peer_id)
|
|
203
|
+
validate_cli_peer_id("bootnode_peer_id", bootnode_peer_id)
|
|
204
|
+
validate_cli_peer_id("client_peer_id", client_peer_id)
|
|
205
|
+
except ValueError as e:
|
|
206
|
+
print_error(str(e))
|
|
207
|
+
raise
|
|
208
|
+
|
|
209
|
+
# STEP 1: Get signing wallet (coldkey) FIRST so we can use it for hotkey disambiguation
|
|
210
|
+
if coldkey:
|
|
211
|
+
from ...utils.wallet.core import resolve_coldkey_and_get_keypair
|
|
212
|
+
|
|
213
|
+
wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
|
|
214
|
+
else:
|
|
215
|
+
wallet_name, keypair = retrieve_wallet_with_validation(
|
|
216
|
+
wallet_type="coldkey", purpose="sign the node registration transaction"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# STEP 2: Collect parameters via prompts (now with coldkey context for hotkey disambiguation)
|
|
220
|
+
request = prompt_node_register(
|
|
221
|
+
subnet_id=subnet_id,
|
|
222
|
+
validator_id=validator_id,
|
|
223
|
+
hotkey=hotkey,
|
|
224
|
+
stake_amount=stake_amount,
|
|
225
|
+
peer_id=peer_id,
|
|
226
|
+
bootnode_peer_id=bootnode_peer_id,
|
|
227
|
+
client_peer_id=client_peer_id,
|
|
228
|
+
coldkey_name=wallet_name, # Pass coldkey name for hotkey disambiguation
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# STEP 3: Execute via client
|
|
232
|
+
client = get_client()
|
|
233
|
+
|
|
234
|
+
# Ensure connection is established
|
|
235
|
+
if not client.connect():
|
|
236
|
+
from ...ui.display import print_error
|
|
237
|
+
print_error("Failed to connect to blockchain")
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
# Check if substrate connection is available
|
|
241
|
+
if not client.substrate:
|
|
242
|
+
from ...ui.display import print_error
|
|
243
|
+
print_error("Blockchain connection failed!")
|
|
244
|
+
raise RuntimeError("Not connected to blockchain")
|
|
245
|
+
|
|
246
|
+
# Check if extrinsics layer is initialized
|
|
247
|
+
if not client.extrinsics:
|
|
248
|
+
from ...ui.display import print_error
|
|
249
|
+
print_error("Extrinsics layer not initialized. Connection may have failed.")
|
|
250
|
+
raise RuntimeError("Extrinsics layer not available")
|
|
251
|
+
|
|
252
|
+
# Get the actual EVM address from wallet info (not from keypair.ss58_address which returns public key)
|
|
253
|
+
from ...utils.logging import get_logger
|
|
254
|
+
from ...utils.wallet.crypto import get_wallet_info_by_name
|
|
255
|
+
|
|
256
|
+
logger = get_logger(__name__)
|
|
257
|
+
wallet_info = get_wallet_info_by_name(wallet_name, is_hotkey=False)
|
|
258
|
+
resolved_coldkey_address = wallet_info.get("evm_address") or wallet_info.get("ss58_address") or wallet_info.get("address")
|
|
259
|
+
|
|
260
|
+
logger.debug(f"Wallet '{wallet_name}' info: {wallet_info}")
|
|
261
|
+
logger.debug(f"keypair.ss58_address: {keypair.ss58_address}")
|
|
262
|
+
logger.debug(f"Resolved coldkey address (from wallet info): {resolved_coldkey_address}")
|
|
263
|
+
logger.debug(f"Using coldkey wallet '{wallet_name}' ({resolved_coldkey_address}) to sign node registration")
|
|
264
|
+
|
|
265
|
+
with HTCLILoadingContext(f"Registering node on subnet {request.subnet_id}..."):
|
|
266
|
+
response = client.extrinsics.node.register_subnet_node(request, keypair)
|
|
267
|
+
|
|
268
|
+
# Check for errors in result
|
|
269
|
+
if isinstance(response, dict) and not response.get("success", False):
|
|
270
|
+
error_msg = response.get("error", "Registration failed")
|
|
271
|
+
if handle_node_error(error_msg, request.subnet_id, None, "register", client):
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
# STEP 4: Display result
|
|
275
|
+
display_node_register_result(response)
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
error_msg = str(e)
|
|
279
|
+
# Extract subnet_id and client if available
|
|
280
|
+
subnet_id_for_error = None
|
|
281
|
+
client_for_error = None
|
|
282
|
+
try:
|
|
283
|
+
subnet_id_for_error = request.subnet_id if 'request' in locals() else None
|
|
284
|
+
client_for_error = client if 'client' in locals() else None
|
|
285
|
+
except (AttributeError, NameError):
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
if not handle_node_error(error_msg, subnet_id_for_error, None, "register", client_for_error):
|
|
289
|
+
handle_and_display_node_error(e, operation="register")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def update_node_handler(
|
|
293
|
+
subnet_id: Optional[int] = None,
|
|
294
|
+
node_id: Optional[int] = None,
|
|
295
|
+
peer_id: Optional[str] = None,
|
|
296
|
+
bootnode: Optional[str] = None,
|
|
297
|
+
bootnode_peer_id: Optional[str] = None,
|
|
298
|
+
client_peer_id: Optional[str] = None,
|
|
299
|
+
delegate_reward_rate: Optional[int] = None,
|
|
300
|
+
unique: Optional[str] = None,
|
|
301
|
+
non_unique: Optional[str] = None,
|
|
302
|
+
coldkey: Optional[str] = None,
|
|
303
|
+
):
|
|
304
|
+
"""
|
|
305
|
+
Unified handler for updating a subnet node.
|
|
306
|
+
|
|
307
|
+
All updatable fields are optional. Any field provided will be updated
|
|
308
|
+
sequentially, and a consolidated summary will be displayed.
|
|
309
|
+
"""
|
|
310
|
+
try:
|
|
311
|
+
from ...ui.display import print_error
|
|
312
|
+
from ...utils.blockchain.peer_id import validate_peer_id_format
|
|
313
|
+
|
|
314
|
+
# Ensure we know which node to update
|
|
315
|
+
if subnet_id is None or node_id is None:
|
|
316
|
+
from ...utils.prompts import integer_prompt
|
|
317
|
+
|
|
318
|
+
if subnet_id is None:
|
|
319
|
+
subnet_id = integer_prompt("Enter the Subnet ID", min_value=0)
|
|
320
|
+
if node_id is None:
|
|
321
|
+
node_id = integer_prompt("Enter the Node ID", min_value=0)
|
|
322
|
+
|
|
323
|
+
# Validate that at least one field is provided
|
|
324
|
+
if (
|
|
325
|
+
peer_id is None
|
|
326
|
+
and bootnode is None
|
|
327
|
+
and bootnode_peer_id is None
|
|
328
|
+
and client_peer_id is None
|
|
329
|
+
and delegate_reward_rate is None
|
|
330
|
+
and unique is None
|
|
331
|
+
and non_unique is None
|
|
332
|
+
):
|
|
333
|
+
print_error(
|
|
334
|
+
"Error: At least one update parameter must be provided "
|
|
335
|
+
"(e.g. --peer-id, --bootnode, --bootnode-peer-id, --client-peer-id, "
|
|
336
|
+
"--delegate-rate, --unique, --non-unique)."
|
|
337
|
+
)
|
|
338
|
+
raise ValueError("No update fields provided")
|
|
339
|
+
|
|
340
|
+
# Basic validation for peer IDs if provided
|
|
341
|
+
def _validate_peer(label: str, value: Optional[str]) -> None:
|
|
342
|
+
if value is None:
|
|
343
|
+
return
|
|
344
|
+
if not isinstance(value, str) or not value:
|
|
345
|
+
raise ValueError(f"{label} must be a non-empty string")
|
|
346
|
+
if not validate_peer_id_format(value):
|
|
347
|
+
raise ValueError(
|
|
348
|
+
f"Invalid {label} format: must be a valid base58-encoded multihash "
|
|
349
|
+
"(e.g., starting with 'Qm', '12D3KooW', or '1')"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
try:
|
|
353
|
+
_validate_peer("peer_id", peer_id)
|
|
354
|
+
_validate_peer("bootnode_peer_id", bootnode_peer_id)
|
|
355
|
+
_validate_peer("client_peer_id", client_peer_id)
|
|
356
|
+
except ValueError as ve:
|
|
357
|
+
print_error(str(ve))
|
|
358
|
+
raise
|
|
359
|
+
|
|
360
|
+
# Normalize delegate reward rate: accept 0-100 as %, otherwise treat as 1e18 format
|
|
361
|
+
delegate_rate_value: Optional[int] = None
|
|
362
|
+
if delegate_reward_rate is not None:
|
|
363
|
+
try:
|
|
364
|
+
delegate_rate_value = normalize_delegate_reward_rate(
|
|
365
|
+
delegate_reward_rate
|
|
366
|
+
)
|
|
367
|
+
except ValueError as ve:
|
|
368
|
+
print_error(f"Invalid delegate reward rate: {ve}")
|
|
369
|
+
raise
|
|
370
|
+
|
|
371
|
+
# Get signing wallet (coldkey)
|
|
372
|
+
if coldkey:
|
|
373
|
+
from ...utils.wallet.core import resolve_coldkey_and_get_keypair
|
|
374
|
+
|
|
375
|
+
wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
|
|
376
|
+
else:
|
|
377
|
+
wallet_name, keypair = retrieve_wallet_with_validation(
|
|
378
|
+
wallet_type="coldkey",
|
|
379
|
+
purpose="sign the node update transaction",
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
client = get_client()
|
|
383
|
+
|
|
384
|
+
# Ensure connection is established
|
|
385
|
+
if not client.connect():
|
|
386
|
+
print_error("Failed to connect to blockchain")
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
if not client.substrate:
|
|
390
|
+
print_error("Blockchain connection failed!")
|
|
391
|
+
raise RuntimeError("Not connected to blockchain")
|
|
392
|
+
|
|
393
|
+
if not client.extrinsics:
|
|
394
|
+
print_error(
|
|
395
|
+
"Extrinsics layer not initialized. Connection may have failed."
|
|
396
|
+
)
|
|
397
|
+
raise RuntimeError("Extrinsics layer not available")
|
|
398
|
+
|
|
399
|
+
# Fetch current node info once for old-value display (best-effort)
|
|
400
|
+
current_node_info = None
|
|
401
|
+
try:
|
|
402
|
+
current_node_info = client.rpc.node.get_subnet_node_info(
|
|
403
|
+
subnet_id, node_id
|
|
404
|
+
)
|
|
405
|
+
except Exception:
|
|
406
|
+
current_node_info = None
|
|
407
|
+
|
|
408
|
+
def _safe_decode_bytes(value: Any) -> Optional[str]:
|
|
409
|
+
if value is None:
|
|
410
|
+
return None
|
|
411
|
+
if isinstance(value, bytes):
|
|
412
|
+
try:
|
|
413
|
+
return value.decode("utf-8", errors="ignore")
|
|
414
|
+
except Exception:
|
|
415
|
+
return str(value)
|
|
416
|
+
return str(value)
|
|
417
|
+
|
|
418
|
+
updates: list[dict[str, Any]] = []
|
|
419
|
+
|
|
420
|
+
# Compute old values (best-effort) for summary
|
|
421
|
+
if peer_id is not None:
|
|
422
|
+
old_peer = (
|
|
423
|
+
_safe_decode_bytes(current_node_info.peer_id)
|
|
424
|
+
if current_node_info is not None
|
|
425
|
+
else None
|
|
426
|
+
)
|
|
427
|
+
else:
|
|
428
|
+
old_peer = None
|
|
429
|
+
|
|
430
|
+
if bootnode is not None:
|
|
431
|
+
old_bootnode = (
|
|
432
|
+
_safe_decode_bytes(current_node_info.bootnode)
|
|
433
|
+
if current_node_info is not None
|
|
434
|
+
else None
|
|
435
|
+
)
|
|
436
|
+
else:
|
|
437
|
+
old_bootnode = None
|
|
438
|
+
|
|
439
|
+
if bootnode_peer_id is not None:
|
|
440
|
+
old_bootnode_peer = (
|
|
441
|
+
_safe_decode_bytes(current_node_info.bootnode_peer_id)
|
|
442
|
+
if current_node_info is not None
|
|
443
|
+
else None
|
|
444
|
+
)
|
|
445
|
+
else:
|
|
446
|
+
old_bootnode_peer = None
|
|
447
|
+
|
|
448
|
+
if client_peer_id is not None:
|
|
449
|
+
old_client_peer = (
|
|
450
|
+
_safe_decode_bytes(current_node_info.client_peer_id)
|
|
451
|
+
if current_node_info is not None
|
|
452
|
+
else None
|
|
453
|
+
)
|
|
454
|
+
else:
|
|
455
|
+
old_client_peer = None
|
|
456
|
+
|
|
457
|
+
if delegate_rate_value is not None:
|
|
458
|
+
old_rate = (
|
|
459
|
+
getattr(current_node_info, "delegate_reward_rate", None)
|
|
460
|
+
if current_node_info is not None
|
|
461
|
+
else None
|
|
462
|
+
)
|
|
463
|
+
else:
|
|
464
|
+
old_rate = None
|
|
465
|
+
|
|
466
|
+
if unique is not None:
|
|
467
|
+
old_unique = (
|
|
468
|
+
_safe_decode_bytes(current_node_info.unique)
|
|
469
|
+
if current_node_info is not None
|
|
470
|
+
else None
|
|
471
|
+
)
|
|
472
|
+
else:
|
|
473
|
+
old_unique = None
|
|
474
|
+
|
|
475
|
+
if non_unique is not None:
|
|
476
|
+
old_non_unique = (
|
|
477
|
+
_safe_decode_bytes(current_node_info.non_unique)
|
|
478
|
+
if current_node_info is not None
|
|
479
|
+
else None
|
|
480
|
+
)
|
|
481
|
+
else:
|
|
482
|
+
old_non_unique = None
|
|
483
|
+
|
|
484
|
+
# Delegate all extrinsic calls to the client layer
|
|
485
|
+
with HTCLILoadingContext(
|
|
486
|
+
f"Updating node {node_id} on subnet {subnet_id}..."
|
|
487
|
+
):
|
|
488
|
+
raw_results = client.extrinsics.node.update_subnet_node_parameters(
|
|
489
|
+
subnet_id=subnet_id,
|
|
490
|
+
subnet_node_id=node_id,
|
|
491
|
+
peer_id=peer_id,
|
|
492
|
+
bootnode=bootnode,
|
|
493
|
+
bootnode_peer_id=bootnode_peer_id,
|
|
494
|
+
client_peer_id=client_peer_id,
|
|
495
|
+
delegate_reward_rate=delegate_rate_value,
|
|
496
|
+
unique=unique,
|
|
497
|
+
non_unique=non_unique,
|
|
498
|
+
keypair=keypair,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# Attach old/new values for summary display
|
|
502
|
+
for entry in raw_results:
|
|
503
|
+
field = entry.get("field")
|
|
504
|
+
result = entry.get("result")
|
|
505
|
+
if field == "peer_id":
|
|
506
|
+
updates.append(
|
|
507
|
+
{
|
|
508
|
+
"field": field,
|
|
509
|
+
"old": old_peer,
|
|
510
|
+
"new": peer_id,
|
|
511
|
+
"result": result,
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
elif field == "bootnode":
|
|
515
|
+
updates.append(
|
|
516
|
+
{
|
|
517
|
+
"field": field,
|
|
518
|
+
"old": old_bootnode,
|
|
519
|
+
"new": bootnode,
|
|
520
|
+
"result": result,
|
|
521
|
+
}
|
|
522
|
+
)
|
|
523
|
+
elif field == "bootnode_peer_id":
|
|
524
|
+
updates.append(
|
|
525
|
+
{
|
|
526
|
+
"field": field,
|
|
527
|
+
"old": old_bootnode_peer,
|
|
528
|
+
"new": bootnode_peer_id,
|
|
529
|
+
"result": result,
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
elif field == "client_peer_id":
|
|
533
|
+
updates.append(
|
|
534
|
+
{
|
|
535
|
+
"field": field,
|
|
536
|
+
"old": old_client_peer,
|
|
537
|
+
"new": client_peer_id,
|
|
538
|
+
"result": result,
|
|
539
|
+
}
|
|
540
|
+
)
|
|
541
|
+
elif field == "delegate_reward_rate":
|
|
542
|
+
updates.append(
|
|
543
|
+
{
|
|
544
|
+
"field": field,
|
|
545
|
+
"old": old_rate,
|
|
546
|
+
"new": delegate_rate_value,
|
|
547
|
+
"result": result,
|
|
548
|
+
}
|
|
549
|
+
)
|
|
550
|
+
elif field == "unique":
|
|
551
|
+
updates.append(
|
|
552
|
+
{
|
|
553
|
+
"field": field,
|
|
554
|
+
"old": old_unique,
|
|
555
|
+
"new": unique,
|
|
556
|
+
"result": result,
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
elif field == "non_unique":
|
|
560
|
+
updates.append(
|
|
561
|
+
{
|
|
562
|
+
"field": field,
|
|
563
|
+
"old": old_non_unique,
|
|
564
|
+
"new": non_unique,
|
|
565
|
+
"result": result,
|
|
566
|
+
}
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
# Check for errors in results
|
|
570
|
+
for entry in updates:
|
|
571
|
+
result = entry.get("result")
|
|
572
|
+
if isinstance(result, dict) and not result.get("success", False):
|
|
573
|
+
error_msg = result.get("error", "Update failed")
|
|
574
|
+
field = entry.get("field", "update")
|
|
575
|
+
if handle_node_error(error_msg, subnet_id, node_id, f"update {field}", client):
|
|
576
|
+
return
|
|
577
|
+
|
|
578
|
+
# Display consolidated summary
|
|
579
|
+
if updates:
|
|
580
|
+
display_node_update_summary(
|
|
581
|
+
subnet_id=subnet_id,
|
|
582
|
+
node_id=node_id,
|
|
583
|
+
updates=updates,
|
|
584
|
+
client=client,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
except Exception as e:
|
|
588
|
+
error_msg = str(e)
|
|
589
|
+
if not handle_node_error(error_msg, subnet_id, node_id, "update", client if 'client' in locals() else None):
|
|
590
|
+
handle_and_display_node_error(e, operation="update")
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def update_node_bootnode_peer_id_handler(
|
|
594
|
+
subnet_id: Optional[int] = None,
|
|
595
|
+
node_id: Optional[int] = None,
|
|
596
|
+
bootnode_peer_id: Optional[str] = None,
|
|
597
|
+
coldkey: Optional[str] = None,
|
|
598
|
+
):
|
|
599
|
+
"""
|
|
600
|
+
Backwards-compatible wrapper to update only the bootnode peer ID.
|
|
601
|
+
"""
|
|
602
|
+
# Collect parameters via prompts if needed, then delegate to unified handler
|
|
603
|
+
if subnet_id is None or node_id is None or bootnode_peer_id is None:
|
|
604
|
+
subnet_id, node_id, peer = prompt_node_peer_id_update(
|
|
605
|
+
subnet_id=subnet_id,
|
|
606
|
+
node_id=node_id,
|
|
607
|
+
peer_id=bootnode_peer_id,
|
|
608
|
+
peer_id_type="bootnode_peer_id",
|
|
609
|
+
)
|
|
610
|
+
bootnode_peer_id = peer
|
|
611
|
+
|
|
612
|
+
update_node_handler(
|
|
613
|
+
subnet_id=subnet_id,
|
|
614
|
+
node_id=node_id,
|
|
615
|
+
bootnode_peer_id=bootnode_peer_id,
|
|
616
|
+
coldkey=coldkey,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def update_node_client_peer_id_handler(
|
|
621
|
+
subnet_id: Optional[int] = None,
|
|
622
|
+
node_id: Optional[int] = None,
|
|
623
|
+
client_peer_id: Optional[str] = None,
|
|
624
|
+
coldkey: Optional[str] = None,
|
|
625
|
+
):
|
|
626
|
+
"""
|
|
627
|
+
Backwards-compatible wrapper to update only the client peer ID.
|
|
628
|
+
"""
|
|
629
|
+
# Collect parameters via prompts if needed, then delegate to unified handler
|
|
630
|
+
if subnet_id is None or node_id is None or client_peer_id is None:
|
|
631
|
+
subnet_id, node_id, peer = prompt_node_peer_id_update(
|
|
632
|
+
subnet_id=subnet_id,
|
|
633
|
+
node_id=node_id,
|
|
634
|
+
peer_id=client_peer_id,
|
|
635
|
+
peer_id_type="client_peer_id",
|
|
636
|
+
)
|
|
637
|
+
client_peer_id = peer
|
|
638
|
+
|
|
639
|
+
update_node_handler(
|
|
640
|
+
subnet_id=subnet_id,
|
|
641
|
+
node_id=node_id,
|
|
642
|
+
client_peer_id=client_peer_id,
|
|
643
|
+
coldkey=coldkey,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def remove_node_handler(
|
|
648
|
+
subnet_id: Optional[int] = None,
|
|
649
|
+
node_id: Optional[int] = None,
|
|
650
|
+
coldkey: Optional[str] = None,
|
|
651
|
+
):
|
|
652
|
+
"""Handle removing a node from a subnet."""
|
|
653
|
+
try:
|
|
654
|
+
from ...models.requests.node import SubnetNodeRemoveRequest
|
|
655
|
+
|
|
656
|
+
# Collect IDs if missing
|
|
657
|
+
if subnet_id is None or node_id is None:
|
|
658
|
+
subnet_id, node_id = prompt_node_remove(subnet_id=subnet_id, node_id=node_id)
|
|
659
|
+
|
|
660
|
+
# Get signing wallet (coldkey)
|
|
661
|
+
if coldkey:
|
|
662
|
+
from ...utils.wallet.core import resolve_coldkey_and_get_keypair
|
|
663
|
+
|
|
664
|
+
wallet_name, keypair = resolve_coldkey_and_get_keypair(coldkey)
|
|
665
|
+
else:
|
|
666
|
+
wallet_name, keypair = retrieve_wallet_with_validation(
|
|
667
|
+
wallet_type="coldkey",
|
|
668
|
+
purpose="sign the node removal transaction",
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
client = get_client()
|
|
672
|
+
|
|
673
|
+
# Ensure connection
|
|
674
|
+
if not client.connect():
|
|
675
|
+
from ...ui.display import print_error
|
|
676
|
+
|
|
677
|
+
print_error("Failed to connect to blockchain")
|
|
678
|
+
return
|
|
679
|
+
|
|
680
|
+
if not client.substrate:
|
|
681
|
+
from ...ui.display import print_error
|
|
682
|
+
|
|
683
|
+
print_error("Blockchain connection failed!")
|
|
684
|
+
raise RuntimeError("Not connected to blockchain")
|
|
685
|
+
|
|
686
|
+
if not client.extrinsics:
|
|
687
|
+
from ...ui.display import print_error
|
|
688
|
+
|
|
689
|
+
print_error(
|
|
690
|
+
"Extrinsics layer not initialized. Connection may have failed."
|
|
691
|
+
)
|
|
692
|
+
raise RuntimeError("Extrinsics layer not available")
|
|
693
|
+
|
|
694
|
+
request = SubnetNodeRemoveRequest(
|
|
695
|
+
subnet_id=subnet_id,
|
|
696
|
+
subnet_node_id=node_id,
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
with HTCLILoadingContext(
|
|
700
|
+
f"Removing node {node_id} from subnet {subnet_id}..."
|
|
701
|
+
):
|
|
702
|
+
result = client.extrinsics.node.remove_subnet_node(request, keypair)
|
|
703
|
+
|
|
704
|
+
# Check for errors in result
|
|
705
|
+
if isinstance(result, dict) and not result.get("success", False):
|
|
706
|
+
error_msg = result.get("error", "Removal failed")
|
|
707
|
+
if handle_node_error(error_msg, subnet_id, node_id, "remove", client):
|
|
708
|
+
return
|
|
709
|
+
|
|
710
|
+
display_node_remove_result(result, subnet_id, node_id)
|
|
711
|
+
|
|
712
|
+
except Exception as e:
|
|
713
|
+
error_msg = str(e)
|
|
714
|
+
if not handle_node_error(error_msg, subnet_id, node_id, "remove", client if 'client' in locals() else None):
|
|
715
|
+
handle_and_display_node_error(e, operation="remove")
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def _resolve_node_lifecycle_ids(
|
|
719
|
+
subnet_id: Optional[int],
|
|
720
|
+
node_id: Optional[int],
|
|
721
|
+
action: str,
|
|
722
|
+
) -> tuple[int, int]:
|
|
723
|
+
"""Prompt for lifecycle command IDs when CLI options are omitted."""
|
|
724
|
+
if subnet_id is None or node_id is None:
|
|
725
|
+
from ...utils.prompts import integer_prompt
|
|
726
|
+
|
|
727
|
+
if subnet_id is None:
|
|
728
|
+
subnet_id = integer_prompt(f"Enter subnet ID to {action} node", min_value=0)
|
|
729
|
+
if node_id is None:
|
|
730
|
+
node_id = integer_prompt(f"Enter node ID to {action}", min_value=0)
|
|
731
|
+
|
|
732
|
+
return subnet_id, node_id
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def _node_lifecycle_handler(
|
|
736
|
+
*,
|
|
737
|
+
subnet_id: Optional[int],
|
|
738
|
+
node_id: Optional[int],
|
|
739
|
+
coldkey: Optional[str],
|
|
740
|
+
action: str,
|
|
741
|
+
):
|
|
742
|
+
"""Submit a node activation/deactivation extrinsic."""
|
|
743
|
+
try:
|
|
744
|
+
from ...models.requests.node import (
|
|
745
|
+
SubnetNodeActivateRequest,
|
|
746
|
+
SubnetNodePauseRequest,
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
subnet_id, node_id = _resolve_node_lifecycle_ids(subnet_id, node_id, action)
|
|
750
|
+
|
|
751
|
+
if coldkey:
|
|
752
|
+
from ...utils.wallet.core import resolve_coldkey_and_get_keypair
|
|
753
|
+
|
|
754
|
+
_, keypair = resolve_coldkey_and_get_keypair(coldkey)
|
|
755
|
+
else:
|
|
756
|
+
_, keypair = retrieve_wallet_with_validation(
|
|
757
|
+
wallet_type="coldkey",
|
|
758
|
+
purpose=f"sign the node {action} transaction",
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
client = get_client()
|
|
762
|
+
|
|
763
|
+
if not client.connect():
|
|
764
|
+
from ...ui.display import print_error
|
|
765
|
+
|
|
766
|
+
print_error("Failed to connect to blockchain")
|
|
767
|
+
return
|
|
768
|
+
|
|
769
|
+
if not client.substrate:
|
|
770
|
+
from ...ui.display import print_error
|
|
771
|
+
|
|
772
|
+
print_error("Blockchain connection failed!")
|
|
773
|
+
raise RuntimeError("Not connected to blockchain")
|
|
774
|
+
|
|
775
|
+
if not client.extrinsics:
|
|
776
|
+
from ...ui.display import print_error
|
|
777
|
+
|
|
778
|
+
print_error(
|
|
779
|
+
"Extrinsics layer not initialized. Connection may have failed."
|
|
780
|
+
)
|
|
781
|
+
raise RuntimeError("Extrinsics layer not available")
|
|
782
|
+
|
|
783
|
+
if action == "activate":
|
|
784
|
+
request = SubnetNodeActivateRequest(
|
|
785
|
+
subnet_id=subnet_id,
|
|
786
|
+
subnet_node_id=node_id,
|
|
787
|
+
)
|
|
788
|
+
submit = client.extrinsics.node.activate_subnet_node
|
|
789
|
+
loading_message = f"Activating node {node_id} on subnet {subnet_id}..."
|
|
790
|
+
elif action == "deactivate":
|
|
791
|
+
request = SubnetNodePauseRequest(
|
|
792
|
+
subnet_id=subnet_id,
|
|
793
|
+
subnet_node_id=node_id,
|
|
794
|
+
)
|
|
795
|
+
submit = client.extrinsics.node.pause_subnet_node
|
|
796
|
+
loading_message = f"Deactivating node {node_id} on subnet {subnet_id}..."
|
|
797
|
+
else:
|
|
798
|
+
raise ValueError(f"Unsupported node lifecycle action: {action}")
|
|
799
|
+
|
|
800
|
+
with HTCLILoadingContext(loading_message):
|
|
801
|
+
result = submit(request, keypair)
|
|
802
|
+
|
|
803
|
+
if isinstance(result, dict) and not result.get("success", False):
|
|
804
|
+
error_msg = result.get("error", f"Node {action} failed")
|
|
805
|
+
if handle_node_error(error_msg, subnet_id, node_id, action, client):
|
|
806
|
+
return
|
|
807
|
+
|
|
808
|
+
display_node_lifecycle_result(result, subnet_id, node_id, action)
|
|
809
|
+
|
|
810
|
+
except Exception as e:
|
|
811
|
+
error_msg = str(e)
|
|
812
|
+
client_for_error = client if "client" in locals() else None
|
|
813
|
+
if not handle_node_error(
|
|
814
|
+
error_msg, subnet_id, node_id, action, client_for_error
|
|
815
|
+
):
|
|
816
|
+
handle_and_display_node_error(e, operation=action)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def activate_node_handler(
|
|
820
|
+
subnet_id: Optional[int] = None,
|
|
821
|
+
node_id: Optional[int] = None,
|
|
822
|
+
coldkey: Optional[str] = None,
|
|
823
|
+
):
|
|
824
|
+
"""Handle activating a node on a subnet."""
|
|
825
|
+
_node_lifecycle_handler(
|
|
826
|
+
subnet_id=subnet_id,
|
|
827
|
+
node_id=node_id,
|
|
828
|
+
coldkey=coldkey,
|
|
829
|
+
action="activate",
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
def deactivate_node_handler(
|
|
834
|
+
subnet_id: Optional[int] = None,
|
|
835
|
+
node_id: Optional[int] = None,
|
|
836
|
+
coldkey: Optional[str] = None,
|
|
837
|
+
):
|
|
838
|
+
"""Handle deactivating a node on a subnet."""
|
|
839
|
+
_node_lifecycle_handler(
|
|
840
|
+
subnet_id=subnet_id,
|
|
841
|
+
node_id=node_id,
|
|
842
|
+
coldkey=coldkey,
|
|
843
|
+
action="deactivate",
|
|
844
|
+
)
|