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,783 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RPC client for subnet node-related queries using scalecodec for decoding.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from substrateinterface import SubstrateInterface
|
|
8
|
+
|
|
9
|
+
from ...models.responses import SubnetNodeInfo
|
|
10
|
+
from ...utils.logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SubnetNodeRpcClient:
|
|
16
|
+
"""RPC client for subnet node-related queries."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, substrate: Optional[SubstrateInterface] = None):
|
|
19
|
+
"""Initialize the subnet node RPC client."""
|
|
20
|
+
self.substrate = substrate
|
|
21
|
+
|
|
22
|
+
def get_subnet_node_info(
|
|
23
|
+
self, subnet_id: int, subnet_node_id: int
|
|
24
|
+
) -> Optional[SubnetNodeInfo]:
|
|
25
|
+
"""
|
|
26
|
+
Get subnet node information using custom RPC method with full SCALE decoding.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
subnet_id: The subnet ID
|
|
30
|
+
subnet_node_id: The subnet node ID
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
SubnetNodeInfo with all fields decoded, or None if not found
|
|
34
|
+
|
|
35
|
+
Reference:
|
|
36
|
+
- mesh-template chain_functions.py lines 1843-1864, 2090-2107
|
|
37
|
+
- hypertensor-evm/pallets/network/src/lib.rs lines 1184-1203
|
|
38
|
+
"""
|
|
39
|
+
# Check if substrate connection exists
|
|
40
|
+
if self.substrate is None:
|
|
41
|
+
logger.error("Cannot get subnet node info: not connected to blockchain")
|
|
42
|
+
raise RuntimeError("Not connected to blockchain")
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Call RPC method
|
|
46
|
+
result = self.substrate.rpc_request(
|
|
47
|
+
method="network_getSubnetNodeInfo", params=[subnet_id, subnet_node_id]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if not result or not result.get("result"):
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
result_data = result["result"]
|
|
54
|
+
|
|
55
|
+
# Decode Option<SubnetNodeInfo> using custom type registry
|
|
56
|
+
from ...utils.blockchain.type_registry import decode_option_subnet_node_info
|
|
57
|
+
|
|
58
|
+
decoded = decode_option_subnet_node_info(result_data)
|
|
59
|
+
|
|
60
|
+
if decoded is None:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
# Convert decoded dict to SubnetNodeInfo model
|
|
64
|
+
def to_bytes(val):
|
|
65
|
+
"""Convert decoded value to bytes for peer IDs and other byte fields."""
|
|
66
|
+
if isinstance(val, bytes):
|
|
67
|
+
return val
|
|
68
|
+
if isinstance(val, list):
|
|
69
|
+
# Vec<u8> from SCALE decoding is a list of integers (0-255)
|
|
70
|
+
# Convert to bytes
|
|
71
|
+
try:
|
|
72
|
+
return bytes(val)
|
|
73
|
+
except (ValueError, TypeError):
|
|
74
|
+
# If conversion fails, try to convert each element
|
|
75
|
+
return bytes(int(b) for b in val if 0 <= int(b) <= 255)
|
|
76
|
+
if isinstance(val, str):
|
|
77
|
+
# String peer IDs should be encoded to UTF-8 bytes
|
|
78
|
+
return val.encode("utf-8")
|
|
79
|
+
# Default: return empty bytes
|
|
80
|
+
return b""
|
|
81
|
+
|
|
82
|
+
def to_address(val):
|
|
83
|
+
from ...utils.blockchain.formatting import to_checksum_address
|
|
84
|
+
|
|
85
|
+
if isinstance(val, str):
|
|
86
|
+
raw_addr = val if val.startswith("0x") else "0x" + val
|
|
87
|
+
else:
|
|
88
|
+
raw_addr = "0x" + (
|
|
89
|
+
bytes(val).hex() if isinstance(val, (list, bytes)) else ""
|
|
90
|
+
)
|
|
91
|
+
return to_checksum_address(raw_addr)
|
|
92
|
+
|
|
93
|
+
# Extract classification (it's a dict with node_class and start_epoch)
|
|
94
|
+
classification_data = decoded.get("classification", {})
|
|
95
|
+
|
|
96
|
+
return SubnetNodeInfo(
|
|
97
|
+
subnet_id=decoded["subnet_id"],
|
|
98
|
+
subnet_node_id=decoded["subnet_node_id"],
|
|
99
|
+
coldkey=to_address(decoded["coldkey"]),
|
|
100
|
+
hotkey=to_address(decoded["hotkey"]),
|
|
101
|
+
peer_id=to_bytes(decoded.get("peer_id", b"")),
|
|
102
|
+
bootnode_peer_id=to_bytes(decoded.get("bootnode_peer_id", b"")),
|
|
103
|
+
client_peer_id=to_bytes(decoded.get("client_peer_id", b"")),
|
|
104
|
+
bootnode=(
|
|
105
|
+
to_bytes(decoded.get("bootnode"))
|
|
106
|
+
if decoded.get("bootnode")
|
|
107
|
+
else None
|
|
108
|
+
),
|
|
109
|
+
identity=decoded.get("identity"),
|
|
110
|
+
classification=classification_data, # Pass dict as-is
|
|
111
|
+
delegate_reward_rate=decoded.get("delegate_reward_rate", 0),
|
|
112
|
+
last_delegate_reward_rate_update=decoded.get(
|
|
113
|
+
"last_delegate_reward_rate_update", 0
|
|
114
|
+
),
|
|
115
|
+
unique=(
|
|
116
|
+
to_bytes(decoded.get("unique")) if decoded.get("unique") else None
|
|
117
|
+
),
|
|
118
|
+
non_unique=(
|
|
119
|
+
to_bytes(decoded.get("non_unique"))
|
|
120
|
+
if decoded.get("non_unique")
|
|
121
|
+
else None
|
|
122
|
+
),
|
|
123
|
+
stake_balance=decoded.get("stake_balance", 0),
|
|
124
|
+
total_node_delegate_stake_shares=decoded.get(
|
|
125
|
+
"total_node_delegate_stake_shares", 0
|
|
126
|
+
),
|
|
127
|
+
node_delegate_stake_balance=decoded.get(
|
|
128
|
+
"node_delegate_stake_balance", 0
|
|
129
|
+
),
|
|
130
|
+
coldkey_reputation=decoded.get("coldkey_reputation"),
|
|
131
|
+
subnet_node_reputation=decoded.get("subnet_node_reputation", 0),
|
|
132
|
+
node_slot_index=decoded.get("node_slot_index"),
|
|
133
|
+
consecutive_idle_epochs=decoded.get("consecutive_idle_epochs", 0),
|
|
134
|
+
consecutive_included_epochs=decoded.get(
|
|
135
|
+
"consecutive_included_epochs", 0
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.error(f"Error getting subnet node info: {e}")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def get_subnet_nodes_info(self, subnet_id: int) -> list[SubnetNodeInfo]:
|
|
144
|
+
"""
|
|
145
|
+
Get information about all nodes in a subnet using custom RPC method with full SCALE decoding.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
subnet_id: The subnet ID
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of SubnetNodeInfo objects with all fields decoded
|
|
152
|
+
|
|
153
|
+
Reference:
|
|
154
|
+
- mesh-template chain_functions.py lines 1619-1634, 2109-2124
|
|
155
|
+
- hypertensor-evm/pallets/network/src/rpc_info/info.rs lines 148+
|
|
156
|
+
"""
|
|
157
|
+
# Check if substrate connection exists
|
|
158
|
+
if self.substrate is None:
|
|
159
|
+
logger.error("Cannot get subnet nodes info: not connected to blockchain")
|
|
160
|
+
raise RuntimeError("Not connected to blockchain")
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
# Call RPC method
|
|
164
|
+
result = self.substrate.rpc_request(
|
|
165
|
+
method="network_getSubnetNodesInfo", params=[subnet_id]
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if not result or not result.get("result"):
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
result_data = result["result"]
|
|
172
|
+
|
|
173
|
+
# Decode Vec<SubnetNodeInfo> using custom type registry
|
|
174
|
+
from ...utils.blockchain.type_registry import decode_vec_subnet_node_info
|
|
175
|
+
|
|
176
|
+
decoded_list = decode_vec_subnet_node_info(result_data)
|
|
177
|
+
|
|
178
|
+
if not decoded_list:
|
|
179
|
+
# Try fallback method when Vec decode returns nothing
|
|
180
|
+
fallback_nodes = self._get_subnet_nodes_info_fallback(subnet_id, [])
|
|
181
|
+
if fallback_nodes:
|
|
182
|
+
return fallback_nodes
|
|
183
|
+
return []
|
|
184
|
+
|
|
185
|
+
# Convert to list of SubnetNodeInfo objects
|
|
186
|
+
def to_bytes(val):
|
|
187
|
+
"""Convert decoded value to bytes for peer IDs and other byte fields."""
|
|
188
|
+
if isinstance(val, bytes):
|
|
189
|
+
return val
|
|
190
|
+
if isinstance(val, list):
|
|
191
|
+
# Vec<u8> from SCALE decoding is a list of integers (0-255)
|
|
192
|
+
# Convert to bytes
|
|
193
|
+
try:
|
|
194
|
+
return bytes(val)
|
|
195
|
+
except (ValueError, TypeError):
|
|
196
|
+
# If conversion fails, try to convert each element
|
|
197
|
+
return bytes(int(b) for b in val if 0 <= int(b) <= 255)
|
|
198
|
+
if isinstance(val, str):
|
|
199
|
+
# String peer IDs should be encoded to UTF-8 bytes
|
|
200
|
+
return val.encode("utf-8")
|
|
201
|
+
# Default: return empty bytes
|
|
202
|
+
return b""
|
|
203
|
+
|
|
204
|
+
def to_address(val):
|
|
205
|
+
from ...utils.blockchain.formatting import to_checksum_address
|
|
206
|
+
|
|
207
|
+
if isinstance(val, str):
|
|
208
|
+
raw_addr = val if val.startswith("0x") else "0x" + val
|
|
209
|
+
else:
|
|
210
|
+
raw_addr = "0x" + (
|
|
211
|
+
bytes(val).hex() if isinstance(val, (list, bytes)) else ""
|
|
212
|
+
)
|
|
213
|
+
return to_checksum_address(raw_addr)
|
|
214
|
+
|
|
215
|
+
nodes = []
|
|
216
|
+
for idx, decoded in enumerate(decoded_list):
|
|
217
|
+
try:
|
|
218
|
+
classification_data = decoded.get("classification", {})
|
|
219
|
+
|
|
220
|
+
node_info = SubnetNodeInfo(
|
|
221
|
+
subnet_id=decoded["subnet_id"],
|
|
222
|
+
subnet_node_id=decoded["subnet_node_id"],
|
|
223
|
+
coldkey=to_address(decoded["coldkey"]),
|
|
224
|
+
hotkey=to_address(decoded["hotkey"]),
|
|
225
|
+
peer_id=to_bytes(decoded["peer_id"]),
|
|
226
|
+
bootnode_peer_id=to_bytes(decoded["bootnode_peer_id"]),
|
|
227
|
+
client_peer_id=to_bytes(decoded["client_peer_id"]),
|
|
228
|
+
bootnode=(
|
|
229
|
+
to_bytes(decoded.get("bootnode"))
|
|
230
|
+
if decoded.get("bootnode")
|
|
231
|
+
else None
|
|
232
|
+
),
|
|
233
|
+
identity=decoded.get("identity"),
|
|
234
|
+
classification=classification_data, # Pass dict as-is
|
|
235
|
+
delegate_reward_rate=decoded.get("delegate_reward_rate", 0),
|
|
236
|
+
last_delegate_reward_rate_update=decoded.get(
|
|
237
|
+
"last_delegate_reward_rate_update", 0
|
|
238
|
+
),
|
|
239
|
+
unique=(
|
|
240
|
+
to_bytes(decoded.get("unique"))
|
|
241
|
+
if decoded.get("unique")
|
|
242
|
+
else None
|
|
243
|
+
),
|
|
244
|
+
non_unique=(
|
|
245
|
+
to_bytes(decoded.get("non_unique"))
|
|
246
|
+
if decoded.get("non_unique")
|
|
247
|
+
else None
|
|
248
|
+
),
|
|
249
|
+
stake_balance=decoded.get("stake_balance", 0),
|
|
250
|
+
node_delegate_stake_balance=decoded.get(
|
|
251
|
+
"node_delegate_stake_balance", 0
|
|
252
|
+
),
|
|
253
|
+
total_node_delegate_stake_shares=decoded.get(
|
|
254
|
+
"total_node_delegate_stake_shares", 0
|
|
255
|
+
),
|
|
256
|
+
coldkey_reputation=decoded.get("coldkey_reputation"),
|
|
257
|
+
subnet_node_reputation=decoded.get("subnet_node_reputation", 0),
|
|
258
|
+
node_slot_index=decoded.get("node_slot_index"),
|
|
259
|
+
consecutive_idle_epochs=decoded.get(
|
|
260
|
+
"consecutive_idle_epochs", 0
|
|
261
|
+
),
|
|
262
|
+
consecutive_included_epochs=decoded.get(
|
|
263
|
+
"consecutive_included_epochs", 0
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
nodes.append(node_info)
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.warning(
|
|
269
|
+
f"Failed to convert node info (idx={idx}, subnet_id={decoded.get('subnet_id')}, node_id={decoded.get('subnet_node_id')}): {e}"
|
|
270
|
+
)
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
# Fallback: If we got fewer nodes than expected (or Vec decode seems incomplete),
|
|
274
|
+
# try querying individual nodes sequentially
|
|
275
|
+
# Check data size to see if there might be more nodes
|
|
276
|
+
data_size = len(result_data) if hasattr(result_data, "__len__") else 0
|
|
277
|
+
if len(nodes) <= 2 and data_size > 100:
|
|
278
|
+
fallback_nodes = self._get_subnet_nodes_info_fallback(subnet_id, nodes)
|
|
279
|
+
if fallback_nodes and len(fallback_nodes) > len(nodes):
|
|
280
|
+
return fallback_nodes
|
|
281
|
+
|
|
282
|
+
return nodes
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logger.error(f"Error getting subnet nodes info: {e}")
|
|
286
|
+
import traceback
|
|
287
|
+
|
|
288
|
+
logger.debug(traceback.format_exc())
|
|
289
|
+
# Try fallback method even on exception
|
|
290
|
+
try:
|
|
291
|
+
fallback_nodes = self._get_subnet_nodes_info_fallback(subnet_id, [])
|
|
292
|
+
if fallback_nodes:
|
|
293
|
+
return fallback_nodes
|
|
294
|
+
except Exception as fallback_error:
|
|
295
|
+
logger.debug(f"Fallback method also failed: {fallback_error}")
|
|
296
|
+
return []
|
|
297
|
+
|
|
298
|
+
def _get_subnet_nodes_info_fallback(
|
|
299
|
+
self, subnet_id: int, existing_nodes: list[SubnetNodeInfo]
|
|
300
|
+
) -> list[SubnetNodeInfo]:
|
|
301
|
+
"""
|
|
302
|
+
Fallback method: Query individual nodes one by one starting from node_id=1
|
|
303
|
+
until we hit a non-existent node.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
subnet_id: The subnet ID
|
|
307
|
+
existing_nodes: Already found nodes from Vec decode (to avoid duplicates)
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
List of all SubnetNodeInfo objects found
|
|
311
|
+
"""
|
|
312
|
+
nodes = []
|
|
313
|
+
existing_node_ids = {node.subnet_node_id for node in existing_nodes}
|
|
314
|
+
|
|
315
|
+
# Start from node_id=1 and increment until we hit a non-existent node
|
|
316
|
+
node_id = 1
|
|
317
|
+
consecutive_not_found = 0
|
|
318
|
+
max_consecutive_not_found = 5 # Stop after 5 consecutive non-existent nodes
|
|
319
|
+
max_nodes = 1000 # Safety limit to prevent infinite loops
|
|
320
|
+
|
|
321
|
+
while (
|
|
322
|
+
node_id <= max_nodes and consecutive_not_found < max_consecutive_not_found
|
|
323
|
+
):
|
|
324
|
+
# Skip if we already have this node from Vec decode
|
|
325
|
+
if node_id in existing_node_ids:
|
|
326
|
+
node_id += 1
|
|
327
|
+
consecutive_not_found = 0 # Reset counter when we find an existing one
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
node_info = self.get_subnet_node_info(subnet_id, node_id)
|
|
332
|
+
if node_info:
|
|
333
|
+
nodes.append(node_info)
|
|
334
|
+
consecutive_not_found = 0 # Reset counter on success
|
|
335
|
+
else:
|
|
336
|
+
consecutive_not_found += 1
|
|
337
|
+
if consecutive_not_found >= max_consecutive_not_found:
|
|
338
|
+
break
|
|
339
|
+
except Exception:
|
|
340
|
+
consecutive_not_found += 1
|
|
341
|
+
if consecutive_not_found >= max_consecutive_not_found:
|
|
342
|
+
break
|
|
343
|
+
|
|
344
|
+
node_id += 1
|
|
345
|
+
|
|
346
|
+
# Combine with existing nodes, removing duplicates
|
|
347
|
+
all_nodes = existing_nodes + nodes
|
|
348
|
+
# Remove duplicates by subnet_node_id (keep first occurrence)
|
|
349
|
+
seen_ids = set()
|
|
350
|
+
unique_nodes = []
|
|
351
|
+
for node in all_nodes:
|
|
352
|
+
if node.subnet_node_id not in seen_ids:
|
|
353
|
+
seen_ids.add(node.subnet_node_id)
|
|
354
|
+
unique_nodes.append(node)
|
|
355
|
+
|
|
356
|
+
# Sort by node_id for consistency
|
|
357
|
+
unique_nodes.sort(key=lambda x: x.subnet_node_id)
|
|
358
|
+
|
|
359
|
+
return unique_nodes
|
|
360
|
+
|
|
361
|
+
def get_all_subnet_nodes_info(self) -> list[SubnetNodeInfo]:
|
|
362
|
+
"""
|
|
363
|
+
Get information about all subnet nodes across all subnets using custom RPC method with full SCALE decoding.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
List of SubnetNodeInfo objects with all fields decoded
|
|
367
|
+
|
|
368
|
+
Reference:
|
|
369
|
+
- mesh-template chain_functions.py lines 1636-1650, 2126-2141
|
|
370
|
+
"""
|
|
371
|
+
# Check if substrate connection exists
|
|
372
|
+
if self.substrate is None:
|
|
373
|
+
logger.error(
|
|
374
|
+
"Cannot get all subnet nodes info: not connected to blockchain"
|
|
375
|
+
)
|
|
376
|
+
raise RuntimeError("Not connected to blockchain")
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
# Call RPC method
|
|
380
|
+
result = self.substrate.rpc_request(
|
|
381
|
+
method="network_getAllSubnetNodesInfo", params=[]
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
if not result or not result.get("result"):
|
|
385
|
+
logger.warning(
|
|
386
|
+
"RPC request returned no result for getAllSubnetNodesInfo"
|
|
387
|
+
)
|
|
388
|
+
return []
|
|
389
|
+
|
|
390
|
+
result_data = result["result"]
|
|
391
|
+
|
|
392
|
+
# Decode Vec<SubnetNodeInfo> using custom type registry
|
|
393
|
+
from ...utils.blockchain.type_registry import decode_vec_subnet_node_info
|
|
394
|
+
|
|
395
|
+
decoded_list = decode_vec_subnet_node_info(result_data)
|
|
396
|
+
|
|
397
|
+
if not decoded_list:
|
|
398
|
+
logger.info("No nodes found across all subnets")
|
|
399
|
+
return []
|
|
400
|
+
|
|
401
|
+
# Convert to list of SubnetNodeInfo objects (same logic as get_subnet_nodes_info)
|
|
402
|
+
def to_bytes(val):
|
|
403
|
+
"""Convert decoded value to bytes for peer IDs and other byte fields."""
|
|
404
|
+
if isinstance(val, bytes):
|
|
405
|
+
return val
|
|
406
|
+
if isinstance(val, list):
|
|
407
|
+
# Vec<u8> from SCALE decoding is a list of integers (0-255)
|
|
408
|
+
# Convert to bytes
|
|
409
|
+
try:
|
|
410
|
+
return bytes(val)
|
|
411
|
+
except (ValueError, TypeError):
|
|
412
|
+
# If conversion fails, try to convert each element
|
|
413
|
+
return bytes(int(b) for b in val if 0 <= int(b) <= 255)
|
|
414
|
+
if isinstance(val, str):
|
|
415
|
+
# String peer IDs should be encoded to UTF-8 bytes
|
|
416
|
+
return val.encode("utf-8")
|
|
417
|
+
# Default: return empty bytes
|
|
418
|
+
return b""
|
|
419
|
+
|
|
420
|
+
def to_address(val):
|
|
421
|
+
from ...utils.blockchain.formatting import to_checksum_address
|
|
422
|
+
|
|
423
|
+
if isinstance(val, str):
|
|
424
|
+
raw_addr = val if val.startswith("0x") else "0x" + val
|
|
425
|
+
else:
|
|
426
|
+
raw_addr = "0x" + (
|
|
427
|
+
bytes(val).hex() if isinstance(val, (list, bytes)) else ""
|
|
428
|
+
)
|
|
429
|
+
return to_checksum_address(raw_addr)
|
|
430
|
+
|
|
431
|
+
nodes = []
|
|
432
|
+
for decoded in decoded_list:
|
|
433
|
+
try:
|
|
434
|
+
classification_data = decoded.get("classification", {})
|
|
435
|
+
|
|
436
|
+
node_info = SubnetNodeInfo(
|
|
437
|
+
subnet_id=decoded["subnet_id"],
|
|
438
|
+
subnet_node_id=decoded["subnet_node_id"],
|
|
439
|
+
coldkey=to_address(decoded["coldkey"]),
|
|
440
|
+
hotkey=to_address(decoded["hotkey"]),
|
|
441
|
+
peer_id=to_bytes(decoded["peer_id"]),
|
|
442
|
+
bootnode_peer_id=to_bytes(decoded["bootnode_peer_id"]),
|
|
443
|
+
client_peer_id=to_bytes(decoded["client_peer_id"]),
|
|
444
|
+
bootnode=(
|
|
445
|
+
to_bytes(decoded.get("bootnode"))
|
|
446
|
+
if decoded.get("bootnode")
|
|
447
|
+
else None
|
|
448
|
+
),
|
|
449
|
+
identity=decoded.get("identity"),
|
|
450
|
+
classification=classification_data, # Pass dict as-is
|
|
451
|
+
delegate_reward_rate=decoded.get("delegate_reward_rate", 0),
|
|
452
|
+
last_delegate_reward_rate_update=decoded.get(
|
|
453
|
+
"last_delegate_reward_rate_update", 0
|
|
454
|
+
),
|
|
455
|
+
unique=(
|
|
456
|
+
to_bytes(decoded.get("unique"))
|
|
457
|
+
if decoded.get("unique")
|
|
458
|
+
else None
|
|
459
|
+
),
|
|
460
|
+
non_unique=(
|
|
461
|
+
to_bytes(decoded.get("non_unique"))
|
|
462
|
+
if decoded.get("non_unique")
|
|
463
|
+
else None
|
|
464
|
+
),
|
|
465
|
+
stake_balance=decoded.get("stake_balance", 0),
|
|
466
|
+
node_delegate_stake_balance=decoded.get(
|
|
467
|
+
"node_delegate_stake_balance", 0
|
|
468
|
+
),
|
|
469
|
+
total_node_delegate_stake_shares=decoded.get(
|
|
470
|
+
"total_node_delegate_stake_shares", 0
|
|
471
|
+
),
|
|
472
|
+
coldkey_reputation=decoded.get("coldkey_reputation"),
|
|
473
|
+
subnet_node_reputation=decoded.get("subnet_node_reputation", 0),
|
|
474
|
+
node_slot_index=decoded.get("node_slot_index"),
|
|
475
|
+
consecutive_idle_epochs=decoded.get(
|
|
476
|
+
"consecutive_idle_epochs", 0
|
|
477
|
+
),
|
|
478
|
+
consecutive_included_epochs=decoded.get(
|
|
479
|
+
"consecutive_included_epochs", 0
|
|
480
|
+
),
|
|
481
|
+
)
|
|
482
|
+
nodes.append(node_info)
|
|
483
|
+
except Exception as e:
|
|
484
|
+
logger.warning(f"Failed to convert node info: {e}")
|
|
485
|
+
continue
|
|
486
|
+
|
|
487
|
+
return nodes
|
|
488
|
+
|
|
489
|
+
except Exception as e:
|
|
490
|
+
logger.error(f"Error getting all subnet nodes info: {e}")
|
|
491
|
+
logger.error(f"Error type: {type(e).__name__}")
|
|
492
|
+
raise # Re-raise the exception so handler can catch it
|
|
493
|
+
|
|
494
|
+
def get_validator_subnet_nodes_info(
|
|
495
|
+
self, validator_id: int
|
|
496
|
+
) -> list[SubnetNodeInfo]:
|
|
497
|
+
"""
|
|
498
|
+
Get subnet node information for a specific validator.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
validator_id: Validator ID
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
List of SubnetNodeInfo objects with all fields decoded
|
|
505
|
+
"""
|
|
506
|
+
try:
|
|
507
|
+
result = self.substrate.rpc_request(
|
|
508
|
+
method="network_getValidatorSubnetNodesInfo", params=[validator_id]
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if not result or not result.get("result"):
|
|
512
|
+
return []
|
|
513
|
+
|
|
514
|
+
result_data = result["result"]
|
|
515
|
+
|
|
516
|
+
from ...utils.blockchain.type_registry import decode_vec_subnet_node_info
|
|
517
|
+
|
|
518
|
+
decoded_list = decode_vec_subnet_node_info(result_data)
|
|
519
|
+
|
|
520
|
+
if not decoded_list:
|
|
521
|
+
return []
|
|
522
|
+
|
|
523
|
+
def to_bytes(val):
|
|
524
|
+
"""Convert decoded value to bytes for peer IDs and byte fields."""
|
|
525
|
+
if isinstance(val, bytes):
|
|
526
|
+
return val
|
|
527
|
+
if isinstance(val, list):
|
|
528
|
+
try:
|
|
529
|
+
return bytes(val)
|
|
530
|
+
except (ValueError, TypeError):
|
|
531
|
+
return bytes(int(b) for b in val if 0 <= int(b) <= 255)
|
|
532
|
+
if isinstance(val, str):
|
|
533
|
+
return val.encode("utf-8")
|
|
534
|
+
return b""
|
|
535
|
+
|
|
536
|
+
def to_address(val):
|
|
537
|
+
from ...utils.blockchain.formatting import to_checksum_address
|
|
538
|
+
|
|
539
|
+
if isinstance(val, str):
|
|
540
|
+
raw_addr = val if val.startswith("0x") else "0x" + val
|
|
541
|
+
else:
|
|
542
|
+
raw_addr = "0x" + (
|
|
543
|
+
bytes(val).hex() if isinstance(val, (list, bytes)) else ""
|
|
544
|
+
)
|
|
545
|
+
return to_checksum_address(raw_addr)
|
|
546
|
+
|
|
547
|
+
nodes = []
|
|
548
|
+
for decoded in decoded_list:
|
|
549
|
+
try:
|
|
550
|
+
classification_data = decoded.get("classification", {})
|
|
551
|
+
|
|
552
|
+
node_info = SubnetNodeInfo(
|
|
553
|
+
subnet_id=decoded["subnet_id"],
|
|
554
|
+
subnet_node_id=decoded["subnet_node_id"],
|
|
555
|
+
coldkey=to_address(decoded["coldkey"]),
|
|
556
|
+
hotkey=to_address(decoded["hotkey"]),
|
|
557
|
+
peer_id=to_bytes(decoded["peer_id"]),
|
|
558
|
+
bootnode_peer_id=to_bytes(decoded["bootnode_peer_id"]),
|
|
559
|
+
client_peer_id=to_bytes(decoded["client_peer_id"]),
|
|
560
|
+
bootnode=(
|
|
561
|
+
to_bytes(decoded.get("bootnode"))
|
|
562
|
+
if decoded.get("bootnode")
|
|
563
|
+
else None
|
|
564
|
+
),
|
|
565
|
+
identity=decoded.get("identity"),
|
|
566
|
+
classification=classification_data,
|
|
567
|
+
delegate_reward_rate=decoded.get("delegate_reward_rate", 0),
|
|
568
|
+
last_delegate_reward_rate_update=decoded.get(
|
|
569
|
+
"last_delegate_reward_rate_update", 0
|
|
570
|
+
),
|
|
571
|
+
unique=(
|
|
572
|
+
to_bytes(decoded.get("unique"))
|
|
573
|
+
if decoded.get("unique")
|
|
574
|
+
else None
|
|
575
|
+
),
|
|
576
|
+
non_unique=(
|
|
577
|
+
to_bytes(decoded.get("non_unique"))
|
|
578
|
+
if decoded.get("non_unique")
|
|
579
|
+
else None
|
|
580
|
+
),
|
|
581
|
+
stake_balance=decoded.get("stake_balance", 0),
|
|
582
|
+
node_delegate_stake_balance=decoded.get(
|
|
583
|
+
"node_delegate_stake_balance", 0
|
|
584
|
+
),
|
|
585
|
+
total_node_delegate_stake_shares=decoded.get(
|
|
586
|
+
"total_node_delegate_stake_shares", 0
|
|
587
|
+
),
|
|
588
|
+
coldkey_reputation=decoded.get("coldkey_reputation"),
|
|
589
|
+
subnet_node_reputation=decoded.get("subnet_node_reputation", 0),
|
|
590
|
+
node_slot_index=decoded.get("node_slot_index"),
|
|
591
|
+
consecutive_idle_epochs=decoded.get(
|
|
592
|
+
"consecutive_idle_epochs", 0
|
|
593
|
+
),
|
|
594
|
+
consecutive_included_epochs=decoded.get(
|
|
595
|
+
"consecutive_included_epochs", 0
|
|
596
|
+
),
|
|
597
|
+
)
|
|
598
|
+
nodes.append(node_info)
|
|
599
|
+
except Exception as e:
|
|
600
|
+
logger.warning(
|
|
601
|
+
"Failed to convert validator node info "
|
|
602
|
+
f"(validator_id={validator_id}, "
|
|
603
|
+
f"subnet_id={decoded.get('subnet_id')}, "
|
|
604
|
+
f"node_id={decoded.get('subnet_node_id')}): {e}"
|
|
605
|
+
)
|
|
606
|
+
continue
|
|
607
|
+
|
|
608
|
+
return nodes
|
|
609
|
+
|
|
610
|
+
except Exception as e:
|
|
611
|
+
logger.error(f"Error getting validator subnet nodes info: {e}")
|
|
612
|
+
import traceback
|
|
613
|
+
|
|
614
|
+
logger.debug(traceback.format_exc())
|
|
615
|
+
return []
|
|
616
|
+
|
|
617
|
+
def get_coldkey_subnet_nodes_info(self, coldkey: str) -> list[SubnetNodeInfo]:
|
|
618
|
+
"""
|
|
619
|
+
Get subnet node information for a specific coldkey using custom RPC method with full SCALE decoding.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
coldkey: The coldkey account ID (0x... format)
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
List of SubnetNodeInfo objects with all fields decoded
|
|
626
|
+
|
|
627
|
+
Reference:
|
|
628
|
+
- mesh-template chain_functions.py lines 1676-1688, 2160-2177
|
|
629
|
+
- hypertensor-evm/pallets/network/rpc/src/lib.rs lines 44-49
|
|
630
|
+
"""
|
|
631
|
+
try:
|
|
632
|
+
# Normalize address format for RPC call (same as wallet.py)
|
|
633
|
+
if coldkey.startswith("0x"):
|
|
634
|
+
# EVM address format - use hex string without 0x prefix, normalize to lowercase
|
|
635
|
+
coldkey_param = coldkey[2:].lower()
|
|
636
|
+
else:
|
|
637
|
+
# SS58 address format - try to decode to hex
|
|
638
|
+
from substrateinterface.utils.ss58 import ss58_decode
|
|
639
|
+
|
|
640
|
+
try:
|
|
641
|
+
coldkey_bytes = ss58_decode(coldkey)
|
|
642
|
+
coldkey_param = coldkey_bytes.hex()
|
|
643
|
+
except Exception:
|
|
644
|
+
# If SS58 decode fails, use address as-is
|
|
645
|
+
coldkey_param = coldkey
|
|
646
|
+
|
|
647
|
+
# Call RPC method
|
|
648
|
+
result = self.substrate.rpc_request(
|
|
649
|
+
method="network_getColdkeySubnetNodesInfo", params=[coldkey_param]
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
if not result or not result.get("result"):
|
|
653
|
+
return []
|
|
654
|
+
|
|
655
|
+
result_data = result["result"]
|
|
656
|
+
|
|
657
|
+
# Decode Vec<SubnetNodeInfo> using custom type registry
|
|
658
|
+
from ...utils.blockchain.type_registry import decode_vec_subnet_node_info
|
|
659
|
+
|
|
660
|
+
decoded_list = decode_vec_subnet_node_info(result_data)
|
|
661
|
+
|
|
662
|
+
if not decoded_list:
|
|
663
|
+
return []
|
|
664
|
+
for idx, decoded in enumerate(decoded_list):
|
|
665
|
+
logger.info(
|
|
666
|
+
f" Node {idx}: subnet_id={decoded.get('subnet_id')}, node_id={decoded.get('subnet_node_id')}, coldkey={decoded.get('coldkey')}, hotkey={decoded.get('hotkey')}"
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
# Convert to list of SubnetNodeInfo objects (same logic as get_subnet_nodes_info)
|
|
670
|
+
def to_bytes(val):
|
|
671
|
+
"""Convert decoded value to bytes for peer IDs and other byte fields."""
|
|
672
|
+
if isinstance(val, bytes):
|
|
673
|
+
return val
|
|
674
|
+
if isinstance(val, list):
|
|
675
|
+
# Vec<u8> from SCALE decoding is a list of integers (0-255)
|
|
676
|
+
# Convert to bytes
|
|
677
|
+
try:
|
|
678
|
+
return bytes(val)
|
|
679
|
+
except (ValueError, TypeError):
|
|
680
|
+
# If conversion fails, try to convert each element
|
|
681
|
+
return bytes(int(b) for b in val if 0 <= int(b) <= 255)
|
|
682
|
+
if isinstance(val, str):
|
|
683
|
+
# String peer IDs should be encoded to UTF-8 bytes
|
|
684
|
+
return val.encode("utf-8")
|
|
685
|
+
# Default: return empty bytes
|
|
686
|
+
return b""
|
|
687
|
+
|
|
688
|
+
def to_address(val):
|
|
689
|
+
from ...utils.blockchain.formatting import to_checksum_address
|
|
690
|
+
|
|
691
|
+
if isinstance(val, str):
|
|
692
|
+
raw_addr = val if val.startswith("0x") else "0x" + val
|
|
693
|
+
else:
|
|
694
|
+
raw_addr = "0x" + (
|
|
695
|
+
bytes(val).hex() if isinstance(val, (list, bytes)) else ""
|
|
696
|
+
)
|
|
697
|
+
return to_checksum_address(raw_addr)
|
|
698
|
+
|
|
699
|
+
nodes = []
|
|
700
|
+
for decoded in decoded_list:
|
|
701
|
+
try:
|
|
702
|
+
classification_data = decoded.get("classification", {})
|
|
703
|
+
|
|
704
|
+
# Log each node being processed
|
|
705
|
+
node_subnet_id = decoded.get("subnet_id")
|
|
706
|
+
node_id = decoded.get("subnet_node_id")
|
|
707
|
+
node_coldkey = to_address(decoded["coldkey"])
|
|
708
|
+
logger.debug(
|
|
709
|
+
f"Processing node {node_id} in subnet {node_subnet_id}, coldkey: {node_coldkey}"
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
node_info = SubnetNodeInfo(
|
|
713
|
+
subnet_id=node_subnet_id,
|
|
714
|
+
subnet_node_id=node_id,
|
|
715
|
+
coldkey=node_coldkey,
|
|
716
|
+
hotkey=to_address(decoded["hotkey"]),
|
|
717
|
+
peer_id=to_bytes(decoded["peer_id"]),
|
|
718
|
+
bootnode_peer_id=to_bytes(decoded["bootnode_peer_id"]),
|
|
719
|
+
client_peer_id=to_bytes(decoded["client_peer_id"]),
|
|
720
|
+
bootnode=(
|
|
721
|
+
to_bytes(decoded.get("bootnode"))
|
|
722
|
+
if decoded.get("bootnode")
|
|
723
|
+
else None
|
|
724
|
+
),
|
|
725
|
+
identity=decoded.get("identity"),
|
|
726
|
+
classification=classification_data, # Pass dict as-is
|
|
727
|
+
delegate_reward_rate=decoded.get("delegate_reward_rate", 0),
|
|
728
|
+
last_delegate_reward_rate_update=decoded.get(
|
|
729
|
+
"last_delegate_reward_rate_update", 0
|
|
730
|
+
),
|
|
731
|
+
unique=(
|
|
732
|
+
to_bytes(decoded.get("unique"))
|
|
733
|
+
if decoded.get("unique")
|
|
734
|
+
else None
|
|
735
|
+
),
|
|
736
|
+
non_unique=(
|
|
737
|
+
to_bytes(decoded.get("non_unique"))
|
|
738
|
+
if decoded.get("non_unique")
|
|
739
|
+
else None
|
|
740
|
+
),
|
|
741
|
+
stake_balance=decoded.get("stake_balance", 0),
|
|
742
|
+
node_delegate_stake_balance=decoded.get(
|
|
743
|
+
"node_delegate_stake_balance", 0
|
|
744
|
+
),
|
|
745
|
+
total_node_delegate_stake_shares=decoded.get(
|
|
746
|
+
"total_node_delegate_stake_shares", 0
|
|
747
|
+
),
|
|
748
|
+
coldkey_reputation=decoded.get("coldkey_reputation"),
|
|
749
|
+
subnet_node_reputation=decoded.get("subnet_node_reputation", 0),
|
|
750
|
+
node_slot_index=decoded.get("node_slot_index"),
|
|
751
|
+
consecutive_idle_epochs=decoded.get(
|
|
752
|
+
"consecutive_idle_epochs", 0
|
|
753
|
+
),
|
|
754
|
+
consecutive_included_epochs=decoded.get(
|
|
755
|
+
"consecutive_included_epochs", 0
|
|
756
|
+
),
|
|
757
|
+
)
|
|
758
|
+
nodes.append(node_info)
|
|
759
|
+
logger.debug(f"Successfully added node {node_id} to list")
|
|
760
|
+
except Exception as e:
|
|
761
|
+
logger.warning(
|
|
762
|
+
f"Failed to convert node info (subnet_id={decoded.get('subnet_id')}, node_id={decoded.get('subnet_node_id')}): {e}"
|
|
763
|
+
)
|
|
764
|
+
import traceback
|
|
765
|
+
|
|
766
|
+
logger.debug(traceback.format_exc())
|
|
767
|
+
continue
|
|
768
|
+
|
|
769
|
+
logger.info(
|
|
770
|
+
f"🔍 DEBUG: Successfully converted {len(nodes)} nodes for coldkey {coldkey}"
|
|
771
|
+
)
|
|
772
|
+
for node in nodes:
|
|
773
|
+
logger.info(
|
|
774
|
+
f" Final node: subnet_id={node.subnet_id}, node_id={node.subnet_node_id}, coldkey={node.coldkey}"
|
|
775
|
+
)
|
|
776
|
+
return nodes
|
|
777
|
+
|
|
778
|
+
except Exception as e:
|
|
779
|
+
logger.error(f"Error getting coldkey subnet nodes info: {e}")
|
|
780
|
+
import traceback
|
|
781
|
+
|
|
782
|
+
logger.debug(traceback.format_exc())
|
|
783
|
+
return []
|