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,1373 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom type registry for SCALE codec decoding of Hypertensor blockchain types.
|
|
3
|
+
|
|
4
|
+
This module provides the complete type definitions needed to properly decode
|
|
5
|
+
RPC responses from the Hypertensor blockchain. The type definitions must match
|
|
6
|
+
the Rust struct definitions in hypertensor-evm/pallets/network/src/lib.rs exactly.
|
|
7
|
+
|
|
8
|
+
Reference:
|
|
9
|
+
- mesh-template/mesh/substrate/chain_data.py (lines 11-258)
|
|
10
|
+
- hypertensor-evm/pallets/network/src/lib.rs (SubnetInfo line 1082, SubnetNodeInfo line 1184)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import copy
|
|
14
|
+
|
|
15
|
+
from scalecodec import ScaleBytes
|
|
16
|
+
from scalecodec.base import RuntimeConfiguration
|
|
17
|
+
from scalecodec.exceptions import RemainingScaleBytesNotEmptyException
|
|
18
|
+
from scalecodec.type_registry import load_type_registry_preset
|
|
19
|
+
|
|
20
|
+
from ..logging import get_logger
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
# Complete custom type registry matching hypertensor-evm blockchain structures
|
|
25
|
+
# Field order MUST match Rust struct definitions exactly for SCALE decoding
|
|
26
|
+
CUSTOM_RPC_TYPE_REGISTRY = {
|
|
27
|
+
"types": {
|
|
28
|
+
# RegistrationSubnetData (used for subnet registration)
|
|
29
|
+
"RegistrationSubnetData": {
|
|
30
|
+
"type": "struct",
|
|
31
|
+
"type_mapping": [
|
|
32
|
+
["name", "Vec<u8>"],
|
|
33
|
+
["repo", "Vec<u8>"],
|
|
34
|
+
["description", "Vec<u8>"],
|
|
35
|
+
["misc", "Vec<u8>"],
|
|
36
|
+
["min_stake", "u128"],
|
|
37
|
+
["max_stake", "u128"],
|
|
38
|
+
["delegate_stake_percentage", "u128"],
|
|
39
|
+
["initial_validators", "BTreeMap<u32, u32>"],
|
|
40
|
+
[
|
|
41
|
+
"bootnodes",
|
|
42
|
+
"BTreeMap<PeerId, BoundedVec<u8, DefaultMaxVectorLength>>",
|
|
43
|
+
],
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
# Subnet Data (basic registration data)
|
|
47
|
+
"SubnetData": {
|
|
48
|
+
"type": "struct",
|
|
49
|
+
"type_mapping": [
|
|
50
|
+
["id", "u32"],
|
|
51
|
+
["friendly_id", "u32"],
|
|
52
|
+
["name", "Vec<u8>"],
|
|
53
|
+
["repo", "Vec<u8>"],
|
|
54
|
+
["description", "Vec<u8>"],
|
|
55
|
+
["misc", "Vec<u8>"],
|
|
56
|
+
["state", "SubnetState"],
|
|
57
|
+
["start_epoch", "u32"],
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
# Complete Subnet Info (RPC helper with aggregated data)
|
|
61
|
+
# Reference: hypertensor-evm/pallets/network/src/lib.rs lines 1082-1116
|
|
62
|
+
"SubnetInfo": {
|
|
63
|
+
"type": "struct",
|
|
64
|
+
"type_mapping": [
|
|
65
|
+
["id", "u32"],
|
|
66
|
+
["friendly_id", "Option<u32>"],
|
|
67
|
+
["name", "Vec<u8>"],
|
|
68
|
+
["repo", "Vec<u8>"],
|
|
69
|
+
["description", "Vec<u8>"],
|
|
70
|
+
["misc", "Vec<u8>"],
|
|
71
|
+
["state", "SubnetState"],
|
|
72
|
+
["start_epoch", "u32"],
|
|
73
|
+
["churn_limit", "u32"],
|
|
74
|
+
# NOTE: churn_limit_multiplier exists in Rust struct but is NOT included
|
|
75
|
+
# in the RPC SubnetInfo response from the deployed testnet. Removed to fix
|
|
76
|
+
# SCALE decoding alignment issue.
|
|
77
|
+
["min_stake", "u128"],
|
|
78
|
+
["max_stake", "u128"],
|
|
79
|
+
["queue_immunity_epochs", "u32"],
|
|
80
|
+
["target_node_registrations_per_epoch", "u32"],
|
|
81
|
+
["node_registrations_this_epoch", "u32"],
|
|
82
|
+
["subnet_node_queue_epochs", "u32"],
|
|
83
|
+
["idle_classification_epochs", "u32"],
|
|
84
|
+
["included_classification_epochs", "u32"],
|
|
85
|
+
["delegate_stake_percentage", "u128"],
|
|
86
|
+
["last_delegate_stake_rewards_update", "u32"],
|
|
87
|
+
["node_burn_rate_alpha", "u128"],
|
|
88
|
+
["current_node_burn_rate", "u128"],
|
|
89
|
+
["initial_coldkeys", "Option<BTreeMap<[u8; 20], u32>>"],
|
|
90
|
+
["initial_coldkey_data", "Option<BTreeMap<[u8; 20], u32>>"],
|
|
91
|
+
["max_registered_nodes", "u32"],
|
|
92
|
+
["owner", "Option<[u8; 20]>"],
|
|
93
|
+
["pending_owner", "Option<[u8; 20]>"],
|
|
94
|
+
["registration_epoch", "Option<u32>"],
|
|
95
|
+
["prev_pause_epoch", "u32"],
|
|
96
|
+
["key_types", "BTreeSet<KeyType>"],
|
|
97
|
+
["slot_index", "Option<u32>"],
|
|
98
|
+
["slot_assignment", "Option<u32>"],
|
|
99
|
+
["subnet_node_min_weight_decrease_reputation_threshold", "u128"],
|
|
100
|
+
["reputation", "u128"],
|
|
101
|
+
["min_subnet_node_reputation", "u128"],
|
|
102
|
+
["absent_decrease_reputation_factor", "u128"],
|
|
103
|
+
["included_increase_reputation_factor", "u128"],
|
|
104
|
+
["below_min_weight_decrease_reputation_factor", "u128"],
|
|
105
|
+
["non_attestor_decrease_reputation_factor", "u128"],
|
|
106
|
+
["non_consensus_attestor_decrease_reputation_factor", "u128"],
|
|
107
|
+
["validator_absent_subnet_node_reputation_factor", "u128"],
|
|
108
|
+
["validator_non_consensus_subnet_node_reputation_factor", "u128"],
|
|
109
|
+
["bootnode_access", "BTreeSet<[u8; 20]>"],
|
|
110
|
+
["bootnodes", "BTreeSet<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
111
|
+
["total_nodes", "u32"],
|
|
112
|
+
["total_active_nodes", "u32"],
|
|
113
|
+
["total_electable_nodes", "u32"],
|
|
114
|
+
["current_min_delegate_stake", "u128"],
|
|
115
|
+
["total_subnet_stake", "u128"],
|
|
116
|
+
["total_subnet_delegate_stake_shares", "u128"],
|
|
117
|
+
["total_subnet_delegate_stake_balance", "u128"],
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
# Subnet State enum
|
|
121
|
+
"SubnetState": {
|
|
122
|
+
"type": "enum",
|
|
123
|
+
"value_list": [
|
|
124
|
+
"Registered",
|
|
125
|
+
"Active",
|
|
126
|
+
"Paused",
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
# Key Type enum (cryptographic key types)
|
|
130
|
+
"KeyType": {
|
|
131
|
+
"type": "enum",
|
|
132
|
+
"value_list": [
|
|
133
|
+
"Rsa",
|
|
134
|
+
"Ed25519",
|
|
135
|
+
"Secp256k1",
|
|
136
|
+
"Ecdsa",
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
# Subnet Node Classification (node class and epoch)
|
|
140
|
+
"SubnetNodeClassification": {
|
|
141
|
+
"type": "struct",
|
|
142
|
+
"type_mapping": [
|
|
143
|
+
["node_class", "SubnetNodeClass"],
|
|
144
|
+
["start_epoch", "u32"],
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
"SubnetNodeClass": {
|
|
148
|
+
"type": "enum",
|
|
149
|
+
"value_list": [
|
|
150
|
+
"Registered",
|
|
151
|
+
"Idle",
|
|
152
|
+
"Included",
|
|
153
|
+
"Validator",
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
# Subnet Node (core node data)
|
|
157
|
+
"SubnetNode": {
|
|
158
|
+
"type": "struct",
|
|
159
|
+
"type_mapping": [
|
|
160
|
+
["id", "u32"],
|
|
161
|
+
["hotkey", "[u8; 20]"],
|
|
162
|
+
["peer_id", "PeerId"],
|
|
163
|
+
["bootnode_peer_id", "PeerId"],
|
|
164
|
+
["client_peer_id", "PeerId"],
|
|
165
|
+
["bootnode", "Option<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
166
|
+
["classification", "SubnetNodeClassification"],
|
|
167
|
+
["delegate_reward_rate", "u128"],
|
|
168
|
+
["last_delegate_reward_rate_update", "u32"],
|
|
169
|
+
["unique", "Option<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
170
|
+
["non_unique", "Option<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
# Subnet Node Info (RPC helper with aggregated data)
|
|
174
|
+
# Reference: hypertensor-evm/pallets/network/src/lib.rs lines 1184-1203
|
|
175
|
+
"SubnetNodeInfo": {
|
|
176
|
+
"type": "struct",
|
|
177
|
+
"type_mapping": [
|
|
178
|
+
["subnet_id", "u32"],
|
|
179
|
+
["subnet_node_id", "u32"],
|
|
180
|
+
["coldkey", "[u8; 20]"],
|
|
181
|
+
["hotkey", "[u8; 20]"],
|
|
182
|
+
["peer_id", "PeerId"],
|
|
183
|
+
["bootnode_peer_id", "PeerId"],
|
|
184
|
+
["client_peer_id", "PeerId"],
|
|
185
|
+
["bootnode", "Option<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
186
|
+
["identity", "ColdkeyIdentityData"],
|
|
187
|
+
["classification", "SubnetNodeClassification"],
|
|
188
|
+
["delegate_reward_rate", "u128"],
|
|
189
|
+
["last_delegate_reward_rate_update", "u32"],
|
|
190
|
+
["unique", "Option<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
191
|
+
["non_unique", "Option<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
192
|
+
["stake_balance", "u128"],
|
|
193
|
+
["total_node_delegate_stake_shares", "u128"],
|
|
194
|
+
["node_delegate_stake_balance", "u128"],
|
|
195
|
+
["coldkey_reputation", "Reputation"],
|
|
196
|
+
["subnet_node_reputation", "u128"],
|
|
197
|
+
["node_slot_index", "Option<u32>"],
|
|
198
|
+
["consecutive_idle_epochs", "u32"],
|
|
199
|
+
["consecutive_included_epochs", "u32"],
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
"ColdkeyIdentityData": {
|
|
203
|
+
"type": "struct",
|
|
204
|
+
"type_mapping": [
|
|
205
|
+
["name", "BoundedVec<u8, DefaultMaxVectorLength>"],
|
|
206
|
+
["url", "BoundedVec<u8, DefaultMaxUrlLength>"],
|
|
207
|
+
["image", "BoundedVec<u8, DefaultMaxUrlLength>"],
|
|
208
|
+
["discord", "BoundedVec<u8, DefaultMaxSocialIdLength>"],
|
|
209
|
+
["x", "BoundedVec<u8, DefaultMaxSocialIdLength>"],
|
|
210
|
+
["telegram", "BoundedVec<u8, DefaultMaxSocialIdLength>"],
|
|
211
|
+
["github", "BoundedVec<u8, DefaultMaxUrlLength>"],
|
|
212
|
+
["hugging_face", "BoundedVec<u8, DefaultMaxUrlLength>"],
|
|
213
|
+
["description", "BoundedVec<u8, DefaultMaxVectorLength>"],
|
|
214
|
+
["misc", "BoundedVec<u8, DefaultMaxVectorLength>"],
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
# Reputation
|
|
218
|
+
"Reputation": {
|
|
219
|
+
"type": "struct",
|
|
220
|
+
"type_mapping": [
|
|
221
|
+
["start_epoch", "u32"],
|
|
222
|
+
["score", "u128"],
|
|
223
|
+
["lifetime_node_count", "u32"],
|
|
224
|
+
["total_active_nodes", "u32"],
|
|
225
|
+
["total_increases", "u32"],
|
|
226
|
+
["total_decreases", "u32"],
|
|
227
|
+
["average_attestation", "u128"],
|
|
228
|
+
["last_validator_epoch", "u32"],
|
|
229
|
+
["ow_score", "u128"],
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
# Stake Info structures
|
|
233
|
+
"SubnetNodeStakeInfo": {
|
|
234
|
+
"type": "struct",
|
|
235
|
+
"type_mapping": [
|
|
236
|
+
["subnet_id", "Option<u32>"],
|
|
237
|
+
["subnet_node_id", "Option<u32>"],
|
|
238
|
+
["hotkey", "[u8; 20]"],
|
|
239
|
+
["balance", "u128"],
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
"NodeStakeInfo": {
|
|
243
|
+
"type": "struct",
|
|
244
|
+
"type_mapping": [
|
|
245
|
+
["subnet_id", "Option<u32>"],
|
|
246
|
+
["subnet_node_id", "Option<u32>"],
|
|
247
|
+
["balance", "u128"],
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
"DelegateStakeInfo": {
|
|
251
|
+
"type": "struct",
|
|
252
|
+
"type_mapping": [
|
|
253
|
+
["subnet_id", "u32"],
|
|
254
|
+
["shares", "u128"],
|
|
255
|
+
["balance", "u128"],
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
"NodeDelegateStakeInfo": {
|
|
259
|
+
"type": "struct",
|
|
260
|
+
"type_mapping": [
|
|
261
|
+
["subnet_id", "u32"],
|
|
262
|
+
["subnet_node_id", "u32"],
|
|
263
|
+
["shares", "u128"],
|
|
264
|
+
["balance", "u128"],
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
# Consensus structures
|
|
268
|
+
"SubnetNodeConsensusData": {
|
|
269
|
+
"type": "struct",
|
|
270
|
+
"type_mapping": [
|
|
271
|
+
["subnet_node_id", "u32"],
|
|
272
|
+
["score", "u128"],
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
"AttestEntry": {
|
|
276
|
+
"type": "struct",
|
|
277
|
+
"type_mapping": [
|
|
278
|
+
["block", "u32"],
|
|
279
|
+
["attestor_progress", "u128"],
|
|
280
|
+
["reward_factor", "u128"],
|
|
281
|
+
["data", "Option<BoundedVec<u8, DefaultValidatorArgsLimit>>"],
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
"ConsensusData": {
|
|
285
|
+
"type": "struct",
|
|
286
|
+
"type_mapping": [
|
|
287
|
+
["validator_id", "u32"],
|
|
288
|
+
["block", "u32"],
|
|
289
|
+
["validator_epoch_progress", "u128"],
|
|
290
|
+
["validator_reward_factor", "u128"],
|
|
291
|
+
["attests", "BTreeMap<u32, AttestEntry>"],
|
|
292
|
+
["subnet_nodes", "Vec<SubnetNode<[u8; 20]>>"],
|
|
293
|
+
["prioritize_queue_node_id", "Option<u32>"],
|
|
294
|
+
["remove_queue_node_id", "Option<u32>"],
|
|
295
|
+
["data", "Vec<SubnetNodeConsensusData>"],
|
|
296
|
+
["args", "Option<BoundedVec<u8, DefaultValidatorArgsLimit>>"],
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
"ConsensusSubmissionData": {
|
|
300
|
+
"type": "struct",
|
|
301
|
+
"type_mapping": [
|
|
302
|
+
["validator_subnet_node_id", "u32"],
|
|
303
|
+
["validator_epoch_progress", "u128"],
|
|
304
|
+
["validator_reward_factor", "u128"],
|
|
305
|
+
["attestation_ratio", "u128"],
|
|
306
|
+
["weight_sum", "u128"],
|
|
307
|
+
["data_length", "u32"],
|
|
308
|
+
["data", "Vec<SubnetNodeConsensusData>"],
|
|
309
|
+
["attests", "BTreeMap<u32, AttestEntry>"],
|
|
310
|
+
["subnet_nodes", "Vec<SubnetNode<[u8; 20]>>"],
|
|
311
|
+
["prioritize_queue_node_id", "Option<u32>"],
|
|
312
|
+
["remove_queue_node_id", "Option<u32>"],
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
"RewardsData": {
|
|
316
|
+
"type": "struct",
|
|
317
|
+
"type_mapping": [
|
|
318
|
+
["overall_subnet_reward", "u128"],
|
|
319
|
+
["subnet_owner_reward", "u128"],
|
|
320
|
+
["subnet_rewards", "u128"],
|
|
321
|
+
["delegate_stake_rewards", "u128"],
|
|
322
|
+
["subnet_node_rewards", "u128"],
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
"AllSubnetBootnodes": {
|
|
326
|
+
"type": "struct",
|
|
327
|
+
"type_mapping": [
|
|
328
|
+
["bootnodes", "BTreeSet<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
329
|
+
["node_bootnodes", "BTreeSet<BoundedVec<u8, DefaultMaxVectorLength>>"],
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
# Overwatch Node types
|
|
333
|
+
# Reference: hypertensor-evm/pallets/network/src/lib.rs lines 1741-1744
|
|
334
|
+
"OverwatchNode": {
|
|
335
|
+
"type": "struct",
|
|
336
|
+
"type_mapping": [
|
|
337
|
+
["id", "u32"],
|
|
338
|
+
["hotkey", "[u8; 20]"],
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
# Reference: hypertensor-evm/pallets/network/src/lib.rs lines 1721-1727
|
|
342
|
+
"OverwatchNodeInfo": {
|
|
343
|
+
"type": "struct",
|
|
344
|
+
"type_mapping": [
|
|
345
|
+
["overwatch_node_id", "u32"],
|
|
346
|
+
["coldkey", "[u8; 20]"],
|
|
347
|
+
["hotkey", "[u8; 20]"],
|
|
348
|
+
["peer_ids", "Vec<PeerId>"],
|
|
349
|
+
["reputation", "Reputation"],
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
# Type aliases for bounded vectors and collections
|
|
353
|
+
"PeerId": "Vec<u8>",
|
|
354
|
+
"OpaquePeerId": "Vec<u8>",
|
|
355
|
+
# AccountId is AccountId20 which is [u8; 20]
|
|
356
|
+
"AccountId": "[u8; 20]",
|
|
357
|
+
"BTreeSet<KeyType>": "Vec<KeyType>",
|
|
358
|
+
"BTreeSet<[u8; 20]>": "Vec<[u8; 20]>",
|
|
359
|
+
"BTreeSet<BoundedVec<u8, DefaultMaxVectorLength>>": "Vec<BoundedVec<u8, DefaultMaxVectorLength>>",
|
|
360
|
+
"BTreeMap<[u8; 20], u32>": "Vec<([u8; 20], u32)>", # BTreeMap decoded as Vec of tuples
|
|
361
|
+
"BTreeMap<AccountId, u32>": "Vec<([u8; 20], u32)>", # AccountId is [u8; 20]
|
|
362
|
+
"BTreeMap<u32, AttestEntry>": "Vec<(u32, AttestEntry)>",
|
|
363
|
+
"BoundedVec<u8, DefaultMaxVectorLength>": "Vec<u8>",
|
|
364
|
+
"BoundedVec<u8, DefaultMaxUrlLength>": "Vec<u8>",
|
|
365
|
+
"BoundedVec<u8, DefaultMaxSocialIdLength>": "Vec<u8>",
|
|
366
|
+
"BoundedVec<u8, DefaultValidatorArgsLimit>": "Vec<u8>",
|
|
367
|
+
"Option<BoundedVec<u8, DefaultMaxVectorLength>>": "Option<Vec<u8>>",
|
|
368
|
+
"Option<BoundedVec<u8, DefaultMaxUrlLength>>": "Option<Vec<u8>>",
|
|
369
|
+
"Option<BoundedVec<u8, DefaultMaxSocialIdLength>>": "Option<Vec<u8>>",
|
|
370
|
+
"Option<BoundedVec<u8, DefaultValidatorArgsLimit>>": "Option<Vec<u8>>",
|
|
371
|
+
"Option<BTreeMap<[u8; 20], u32>>": "Option<Vec<([u8; 20], u32)>>",
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
LEGACY_RPC_TYPE_REGISTRY = copy.deepcopy(CUSTOM_RPC_TYPE_REGISTRY)
|
|
376
|
+
LEGACY_RPC_TYPE_REGISTRY["types"]["SubnetData"]["type_mapping"] = [
|
|
377
|
+
mapping
|
|
378
|
+
for mapping in CUSTOM_RPC_TYPE_REGISTRY["types"]["SubnetData"]["type_mapping"]
|
|
379
|
+
if mapping[0] != "friendly_id"
|
|
380
|
+
]
|
|
381
|
+
LEGACY_RPC_TYPE_REGISTRY["types"]["SubnetInfo"]["type_mapping"] = [
|
|
382
|
+
mapping
|
|
383
|
+
for mapping in CUSTOM_RPC_TYPE_REGISTRY["types"]["SubnetInfo"]["type_mapping"]
|
|
384
|
+
if mapping[0] != "friendly_id"
|
|
385
|
+
]
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def get_rpc_runtime_config(use_legacy: bool = False) -> RuntimeConfiguration:
|
|
389
|
+
"""
|
|
390
|
+
Get properly configured RuntimeConfiguration for RPC SCALE decoding.
|
|
391
|
+
|
|
392
|
+
This creates a RuntimeConfiguration with both the legacy type registry
|
|
393
|
+
and our custom Hypertensor types, matching the pattern from mesh-template.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
use_legacy: Build a config that matches the legacy subnet schema.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
RuntimeConfiguration configured for decoding Hypertensor RPC responses.
|
|
400
|
+
"""
|
|
401
|
+
config = RuntimeConfiguration()
|
|
402
|
+
config.update_type_registry(load_type_registry_preset("legacy"))
|
|
403
|
+
config.update_type_registry(
|
|
404
|
+
LEGACY_RPC_TYPE_REGISTRY if use_legacy else CUSTOM_RPC_TYPE_REGISTRY
|
|
405
|
+
)
|
|
406
|
+
return config
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _normalize_scale_input(data):
|
|
410
|
+
if isinstance(data, list):
|
|
411
|
+
return bytes(data)
|
|
412
|
+
if isinstance(data, str):
|
|
413
|
+
hex_data = data[2:] if data.startswith("0x") else data
|
|
414
|
+
return bytes.fromhex(hex_data)
|
|
415
|
+
if isinstance(data, bytes):
|
|
416
|
+
return data
|
|
417
|
+
return bytes(data)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _decode_scale_object(data, type_name: str, allow_legacy_fallback: bool = False):
|
|
421
|
+
normalized = _normalize_scale_input(data)
|
|
422
|
+
configs = [get_rpc_runtime_config()]
|
|
423
|
+
if allow_legacy_fallback:
|
|
424
|
+
configs.append(get_rpc_runtime_config(use_legacy=True))
|
|
425
|
+
|
|
426
|
+
last_error: Exception | None = None
|
|
427
|
+
for idx, config in enumerate(configs):
|
|
428
|
+
obj = config.create_scale_object(type_name)
|
|
429
|
+
try:
|
|
430
|
+
decoded = obj.decode(ScaleBytes(normalized))
|
|
431
|
+
if allow_legacy_fallback and idx == 1:
|
|
432
|
+
logger.info("Decoded %s with legacy registry", type_name)
|
|
433
|
+
return decoded
|
|
434
|
+
except RemainingScaleBytesNotEmptyException as exc:
|
|
435
|
+
last_error = exc
|
|
436
|
+
if allow_legacy_fallback and idx == 0:
|
|
437
|
+
logger.warning(
|
|
438
|
+
"Remaining SCALE bytes when decoding %s with latest schema (%s); "
|
|
439
|
+
"retrying legacy registry",
|
|
440
|
+
type_name,
|
|
441
|
+
exc,
|
|
442
|
+
)
|
|
443
|
+
continue
|
|
444
|
+
raise
|
|
445
|
+
except Exception as exc: # pragma: no cover
|
|
446
|
+
last_error = exc
|
|
447
|
+
if allow_legacy_fallback and idx == 0:
|
|
448
|
+
logger.warning(
|
|
449
|
+
"Error decoding %s with latest schema (%s); retrying legacy registry",
|
|
450
|
+
type_name,
|
|
451
|
+
exc,
|
|
452
|
+
)
|
|
453
|
+
continue
|
|
454
|
+
raise
|
|
455
|
+
|
|
456
|
+
if last_error:
|
|
457
|
+
raise last_error
|
|
458
|
+
return None
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def decode_option_subnet_info(data: bytes) -> dict:
|
|
462
|
+
"""
|
|
463
|
+
Decode Option<SubnetInfo> from SCALE bytes.
|
|
464
|
+
|
|
465
|
+
This function handles the case where decoding raises RemainingScaleBytesNotEmptyException
|
|
466
|
+
but the decoded value is still available in obj.value. It uses the same approach as
|
|
467
|
+
decode_vec_subnet_info - decode the SubnetInfo directly and extract the value even when
|
|
468
|
+
an exception is raised.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
data: SCALE-encoded bytes (list of ints or bytes object)
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Decoded SubnetInfo dict or None if subnet not found
|
|
475
|
+
"""
|
|
476
|
+
normalized = _normalize_scale_input(data)
|
|
477
|
+
if not normalized:
|
|
478
|
+
return None
|
|
479
|
+
|
|
480
|
+
config = get_rpc_runtime_config()
|
|
481
|
+
|
|
482
|
+
# Check first byte to see if Option is Some (0x01) or None (0x00)
|
|
483
|
+
if len(normalized) == 0:
|
|
484
|
+
return None
|
|
485
|
+
|
|
486
|
+
option_byte = normalized[0]
|
|
487
|
+
if option_byte == 0x00:
|
|
488
|
+
# Option::None
|
|
489
|
+
return None
|
|
490
|
+
elif option_byte == 0x01:
|
|
491
|
+
# Option::Some - decode the SubnetInfo
|
|
492
|
+
# Skip the Option discriminant byte (0x01)
|
|
493
|
+
subnet_bytes = normalized[1:]
|
|
494
|
+
|
|
495
|
+
# Decode SubnetInfo directly (same approach as decode_vec_subnet_info)
|
|
496
|
+
subnet_obj = config.create_scale_object("SubnetInfo")
|
|
497
|
+
try:
|
|
498
|
+
subnet_obj.decode(ScaleBytes(subnet_bytes))
|
|
499
|
+
# Decode succeeded without exception
|
|
500
|
+
except RemainingScaleBytesNotEmptyException:
|
|
501
|
+
# Exception is expected - scalecodec sets obj.value before raising it
|
|
502
|
+
pass
|
|
503
|
+
except Exception as e:
|
|
504
|
+
logger.error(f"Failed to decode SubnetInfo in Option: {e}")
|
|
505
|
+
return None
|
|
506
|
+
|
|
507
|
+
# Extract decoded value (always set even when exception was raised)
|
|
508
|
+
if hasattr(subnet_obj, "value") and subnet_obj.value:
|
|
509
|
+
return subnet_obj.value
|
|
510
|
+
|
|
511
|
+
logger.error("Failed to decode SubnetInfo from Option - no value extracted")
|
|
512
|
+
return None
|
|
513
|
+
else:
|
|
514
|
+
logger.error(f"Invalid Option discriminant byte: {option_byte}")
|
|
515
|
+
return None
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def decode_vec_subnet_info(data: bytes) -> list[dict]:
|
|
519
|
+
"""
|
|
520
|
+
Decode Vec<SubnetInfo> from SCALE bytes.
|
|
521
|
+
|
|
522
|
+
This function attempts to decode the entire Vec<SubnetInfo> at once using scalecodec,
|
|
523
|
+
and extracts all decoded values from the Vec object even if exceptions are raised.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
data: SCALE-encoded bytes (includes Vec length byte at the start)
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
List of decoded SubnetInfo dicts
|
|
530
|
+
"""
|
|
531
|
+
normalized = _normalize_scale_input(data)
|
|
532
|
+
if not normalized:
|
|
533
|
+
logger.warning("No data provided to decode_vec_subnet_info")
|
|
534
|
+
return []
|
|
535
|
+
|
|
536
|
+
config = get_rpc_runtime_config()
|
|
537
|
+
|
|
538
|
+
try:
|
|
539
|
+
# Try direct Vec decoding - scalecodec should handle Vec<SubnetInfo>
|
|
540
|
+
vec_obj = config.create_scale_object("Vec<SubnetInfo>")
|
|
541
|
+
|
|
542
|
+
# Decode and catch exceptions - value may still be populated
|
|
543
|
+
exception_occurred = False
|
|
544
|
+
exception_msg = None
|
|
545
|
+
try:
|
|
546
|
+
vec_obj.decode(ScaleBytes(normalized))
|
|
547
|
+
logger.debug("Vec<SubnetInfo> decoded without exception")
|
|
548
|
+
except RemainingScaleBytesNotEmptyException as e:
|
|
549
|
+
# This is expected - Vec decoding may raise this even when values are decoded
|
|
550
|
+
exception_occurred = True
|
|
551
|
+
exception_msg = str(e)
|
|
552
|
+
logger.debug(
|
|
553
|
+
f"RemainingScaleBytesNotEmptyException during Vec decode: {exception_msg}"
|
|
554
|
+
)
|
|
555
|
+
except Exception as e:
|
|
556
|
+
logger.warning(
|
|
557
|
+
f"Exception during Vec decode: {e}, trying to extract partial results"
|
|
558
|
+
)
|
|
559
|
+
exception_occurred = True
|
|
560
|
+
exception_msg = str(e)
|
|
561
|
+
|
|
562
|
+
# Try to extract decoded values from the Vec object
|
|
563
|
+
# scalecodec's Vec type stores elements in different places depending on the state
|
|
564
|
+
decoded_list = None
|
|
565
|
+
|
|
566
|
+
# Method 1: Direct value attribute (most common)
|
|
567
|
+
if hasattr(vec_obj, "value") and vec_obj.value is not None:
|
|
568
|
+
decoded_list = vec_obj.value
|
|
569
|
+
logger.debug(
|
|
570
|
+
f"Found value in vec_obj.value: {type(decoded_list)}, length: {len(decoded_list) if isinstance(decoded_list, list) else 'N/A'}"
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
# Method 2: Check if Vec has a value_list attribute (some Vec implementations)
|
|
574
|
+
if (
|
|
575
|
+
decoded_list is None
|
|
576
|
+
or (isinstance(decoded_list, list) and len(decoded_list) == 0)
|
|
577
|
+
) and hasattr(vec_obj, "value_list"):
|
|
578
|
+
if vec_obj.value_list:
|
|
579
|
+
decoded_list = vec_obj.value_list
|
|
580
|
+
logger.debug(
|
|
581
|
+
f"Found value in vec_obj.value_list: {len(decoded_list)} items"
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# Method 3: Try to access elements via Vec's internal structure
|
|
585
|
+
# scalecodec Vec objects may have _elements or elements attributes
|
|
586
|
+
if decoded_list is None or (
|
|
587
|
+
isinstance(decoded_list, list) and len(decoded_list) == 0
|
|
588
|
+
):
|
|
589
|
+
# Try accessing internal elements
|
|
590
|
+
for attr_name in [
|
|
591
|
+
"elements",
|
|
592
|
+
"_elements",
|
|
593
|
+
"decoded_elements",
|
|
594
|
+
"value_serialized",
|
|
595
|
+
]:
|
|
596
|
+
if hasattr(vec_obj, attr_name):
|
|
597
|
+
try:
|
|
598
|
+
attr_value = getattr(vec_obj, attr_name)
|
|
599
|
+
if attr_value is not None:
|
|
600
|
+
# Check if it's a list or something we can convert
|
|
601
|
+
if isinstance(attr_value, list) and len(attr_value) > 0:
|
|
602
|
+
decoded_list = attr_value
|
|
603
|
+
logger.debug(
|
|
604
|
+
f"Found value in vec_obj.{attr_name}: {len(decoded_list)} items"
|
|
605
|
+
)
|
|
606
|
+
break
|
|
607
|
+
elif hasattr(attr_value, "__iter__") and not isinstance(
|
|
608
|
+
attr_value, (str, bytes)
|
|
609
|
+
):
|
|
610
|
+
# Try to convert to list
|
|
611
|
+
try:
|
|
612
|
+
decoded_list = list(attr_value)
|
|
613
|
+
if decoded_list:
|
|
614
|
+
logger.debug(
|
|
615
|
+
f"Found value in vec_obj.{attr_name} (converted): {len(decoded_list)} items"
|
|
616
|
+
)
|
|
617
|
+
break
|
|
618
|
+
except Exception:
|
|
619
|
+
pass
|
|
620
|
+
except Exception as attr_err:
|
|
621
|
+
logger.debug(
|
|
622
|
+
f"Could not access vec_obj.{attr_name}: {attr_err}"
|
|
623
|
+
)
|
|
624
|
+
continue
|
|
625
|
+
|
|
626
|
+
# Normalize to list
|
|
627
|
+
if decoded_list is not None:
|
|
628
|
+
if not isinstance(decoded_list, list):
|
|
629
|
+
decoded_list = [decoded_list]
|
|
630
|
+
|
|
631
|
+
# Filter out None values
|
|
632
|
+
decoded_list = [item for item in decoded_list if item is not None]
|
|
633
|
+
|
|
634
|
+
if decoded_list:
|
|
635
|
+
logger.info(
|
|
636
|
+
f"Successfully decoded {len(decoded_list)} subnet(s) from Vec<SubnetInfo> "
|
|
637
|
+
f"(exception occurred: {exception_occurred})"
|
|
638
|
+
)
|
|
639
|
+
return decoded_list
|
|
640
|
+
|
|
641
|
+
logger.warning(
|
|
642
|
+
"Could not extract decoded values from Vec object using direct Vec decode"
|
|
643
|
+
)
|
|
644
|
+
logger.debug(
|
|
645
|
+
"Attempting to manually process Vec elements using Vec's internal decoder"
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
# Try to use Vec's internal decoder to process elements one by one
|
|
649
|
+
try:
|
|
650
|
+
# Access Vec's data and sub_type to decode elements manually
|
|
651
|
+
vec_length_byte = normalized[0]
|
|
652
|
+
|
|
653
|
+
# Read Vec length
|
|
654
|
+
if (vec_length_byte & 0x03) == 0x00:
|
|
655
|
+
vec_length = vec_length_byte >> 2
|
|
656
|
+
element_offset = 1
|
|
657
|
+
elif (vec_length_byte & 0x03) == 0x01:
|
|
658
|
+
vec_length = ((vec_length_byte >> 2) | (normalized[1] << 6)) & 0x3FFF
|
|
659
|
+
element_offset = 2
|
|
660
|
+
elif (vec_length_byte & 0x03) == 0x02:
|
|
661
|
+
vec_length = (
|
|
662
|
+
((vec_length_byte >> 2) & 0x3F)
|
|
663
|
+
| (normalized[1] << 6)
|
|
664
|
+
| (normalized[2] << 14)
|
|
665
|
+
| (normalized[3] << 22)
|
|
666
|
+
) & 0x3FFFFFFF
|
|
667
|
+
element_offset = 4
|
|
668
|
+
else:
|
|
669
|
+
vec_length = 0
|
|
670
|
+
element_offset = 1
|
|
671
|
+
|
|
672
|
+
logger.debug(
|
|
673
|
+
f"Vec length from compact encoding: {vec_length}, elements start at offset {element_offset}"
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
# Try to use Vec's sub_type decoder to decode each element
|
|
677
|
+
if hasattr(vec_obj, "sub_type") and vec_length > 0:
|
|
678
|
+
decoded_elements = []
|
|
679
|
+
current_offset = element_offset
|
|
680
|
+
|
|
681
|
+
for i in range(vec_length):
|
|
682
|
+
if current_offset >= len(normalized):
|
|
683
|
+
logger.warning(
|
|
684
|
+
f"Ran out of bytes at element {i + 1}/{vec_length}"
|
|
685
|
+
)
|
|
686
|
+
break
|
|
687
|
+
|
|
688
|
+
# Create a SubnetInfo decoder
|
|
689
|
+
element_obj = config.create_scale_object("SubnetInfo")
|
|
690
|
+
element_bytes = normalized[current_offset:]
|
|
691
|
+
|
|
692
|
+
try:
|
|
693
|
+
element_obj.decode(ScaleBytes(element_bytes))
|
|
694
|
+
# Check if we got a value
|
|
695
|
+
if (
|
|
696
|
+
hasattr(element_obj, "value")
|
|
697
|
+
and element_obj.value is not None
|
|
698
|
+
):
|
|
699
|
+
decoded_elements.append(element_obj.value)
|
|
700
|
+
# Get consumed bytes
|
|
701
|
+
if hasattr(element_obj, "data") and hasattr(
|
|
702
|
+
element_obj.data, "offset"
|
|
703
|
+
):
|
|
704
|
+
consumed = element_obj.data.offset
|
|
705
|
+
if 0 < consumed <= len(element_bytes):
|
|
706
|
+
current_offset += consumed
|
|
707
|
+
logger.debug(
|
|
708
|
+
f"Element {i + 1} decoded, consumed {consumed} bytes, new offset: {current_offset}"
|
|
709
|
+
)
|
|
710
|
+
else:
|
|
711
|
+
logger.warning(
|
|
712
|
+
f"Invalid consumed bytes {consumed} for element {i + 1}, estimating"
|
|
713
|
+
)
|
|
714
|
+
# Estimate based on average
|
|
715
|
+
estimated = (
|
|
716
|
+
len(normalized) // vec_length
|
|
717
|
+
if vec_length > 0
|
|
718
|
+
else 500
|
|
719
|
+
)
|
|
720
|
+
current_offset += min(estimated, len(element_bytes))
|
|
721
|
+
else:
|
|
722
|
+
# Estimate
|
|
723
|
+
estimated = (
|
|
724
|
+
len(normalized) // vec_length
|
|
725
|
+
if vec_length > 0
|
|
726
|
+
else 500
|
|
727
|
+
)
|
|
728
|
+
current_offset += min(estimated, len(element_bytes))
|
|
729
|
+
else:
|
|
730
|
+
logger.warning(f"Element {i + 1} decode returned no value")
|
|
731
|
+
break
|
|
732
|
+
except RemainingScaleBytesNotEmptyException as e:
|
|
733
|
+
# Exception is OK if we got a value
|
|
734
|
+
if (
|
|
735
|
+
hasattr(element_obj, "value")
|
|
736
|
+
and element_obj.value is not None
|
|
737
|
+
):
|
|
738
|
+
decoded_elements.append(element_obj.value)
|
|
739
|
+
# Try to get consumed bytes
|
|
740
|
+
if hasattr(element_obj, "data") and hasattr(
|
|
741
|
+
element_obj.data, "offset"
|
|
742
|
+
):
|
|
743
|
+
consumed = element_obj.data.offset
|
|
744
|
+
if 0 < consumed <= len(element_bytes):
|
|
745
|
+
current_offset += consumed
|
|
746
|
+
else:
|
|
747
|
+
# Try to extract from exception
|
|
748
|
+
exc_msg = str(e)
|
|
749
|
+
if "Current offset:" in exc_msg:
|
|
750
|
+
try:
|
|
751
|
+
consumed = int(
|
|
752
|
+
exc_msg.split("Current offset:")[1]
|
|
753
|
+
.split("/")[0]
|
|
754
|
+
.strip()
|
|
755
|
+
)
|
|
756
|
+
if 0 < consumed <= len(element_bytes):
|
|
757
|
+
current_offset += consumed
|
|
758
|
+
else:
|
|
759
|
+
break
|
|
760
|
+
except Exception:
|
|
761
|
+
break
|
|
762
|
+
else:
|
|
763
|
+
# Estimate
|
|
764
|
+
estimated = (
|
|
765
|
+
len(normalized) // vec_length
|
|
766
|
+
if vec_length > 0
|
|
767
|
+
else 500
|
|
768
|
+
)
|
|
769
|
+
current_offset += min(
|
|
770
|
+
estimated, len(element_bytes)
|
|
771
|
+
)
|
|
772
|
+
else:
|
|
773
|
+
break
|
|
774
|
+
else:
|
|
775
|
+
logger.warning(
|
|
776
|
+
f"Element {i + 1} decode failed with no value: {e}"
|
|
777
|
+
)
|
|
778
|
+
break
|
|
779
|
+
except Exception as e:
|
|
780
|
+
logger.error(f"Element {i + 1} decode failed: {e}")
|
|
781
|
+
break
|
|
782
|
+
|
|
783
|
+
if decoded_elements and len(decoded_elements) == vec_length:
|
|
784
|
+
logger.info(
|
|
785
|
+
f"Successfully decoded all {len(decoded_elements)} subnet(s) using Vec element iteration"
|
|
786
|
+
)
|
|
787
|
+
return decoded_elements
|
|
788
|
+
elif decoded_elements:
|
|
789
|
+
logger.warning(
|
|
790
|
+
f"Only decoded {len(decoded_elements)}/{vec_length} elements using Vec iteration"
|
|
791
|
+
)
|
|
792
|
+
# Don't return partial - let it fall through to manual iteration
|
|
793
|
+
except Exception as e:
|
|
794
|
+
logger.debug(
|
|
795
|
+
f"Vec element iteration failed: {e}, falling back to manual iteration"
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
# Manual iteration approach: decode Vec length, then each SubnetInfo individually
|
|
799
|
+
logger.debug("Attempting manual iteration for Vec<SubnetInfo>")
|
|
800
|
+
|
|
801
|
+
# Read Vec length (compact-encoded)
|
|
802
|
+
if len(normalized) < 1:
|
|
803
|
+
logger.error("Not enough data to read Vec length")
|
|
804
|
+
return []
|
|
805
|
+
|
|
806
|
+
vec_length_byte = normalized[0]
|
|
807
|
+
# Compact encoding: first 2 bits determine format
|
|
808
|
+
# 00 = single byte (max 63), 01 = 2 bytes, 10 = 4 bytes, 11 = large integer
|
|
809
|
+
if (vec_length_byte & 0x03) == 0x00:
|
|
810
|
+
vec_length = vec_length_byte >> 2
|
|
811
|
+
offset = 1
|
|
812
|
+
elif (vec_length_byte & 0x03) == 0x01:
|
|
813
|
+
# 2-byte compact
|
|
814
|
+
if len(normalized) < 2:
|
|
815
|
+
logger.error("Not enough data for 2-byte compact length")
|
|
816
|
+
return []
|
|
817
|
+
vec_length = ((vec_length_byte >> 2) | (normalized[1] << 6)) & 0x3FFF
|
|
818
|
+
offset = 2
|
|
819
|
+
elif (vec_length_byte & 0x03) == 0x02:
|
|
820
|
+
# 4-byte compact
|
|
821
|
+
if len(normalized) < 4:
|
|
822
|
+
logger.error("Not enough data for 4-byte compact length")
|
|
823
|
+
return []
|
|
824
|
+
vec_length = (
|
|
825
|
+
((vec_length_byte >> 2) & 0x3F)
|
|
826
|
+
| (normalized[1] << 6)
|
|
827
|
+
| (normalized[2] << 14)
|
|
828
|
+
| (normalized[3] << 22)
|
|
829
|
+
) & 0x3FFFFFFF
|
|
830
|
+
offset = 4
|
|
831
|
+
else:
|
|
832
|
+
logger.error("Large integer compact encoding not supported for Vec length")
|
|
833
|
+
return []
|
|
834
|
+
|
|
835
|
+
logger.debug(f"Vec length: {vec_length}, starting at offset {offset}")
|
|
836
|
+
|
|
837
|
+
decoded_subnets = []
|
|
838
|
+
|
|
839
|
+
# Decode each SubnetInfo individually
|
|
840
|
+
for i in range(vec_length):
|
|
841
|
+
if offset >= len(normalized):
|
|
842
|
+
logger.warning(
|
|
843
|
+
f"Ran out of data while decoding subnet {i + 1}/{vec_length} at offset {offset}"
|
|
844
|
+
)
|
|
845
|
+
break
|
|
846
|
+
|
|
847
|
+
subnet_bytes = normalized[offset:]
|
|
848
|
+
logger.debug(
|
|
849
|
+
f"Decoding subnet {i + 1}/{vec_length} at offset {offset}, {len(subnet_bytes)} bytes remaining"
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
single_obj = config.create_scale_object("SubnetInfo")
|
|
853
|
+
exception_raised = None
|
|
854
|
+
consumed_bytes = 0
|
|
855
|
+
|
|
856
|
+
try:
|
|
857
|
+
single_obj.decode(ScaleBytes(subnet_bytes))
|
|
858
|
+
# Success - no exception
|
|
859
|
+
except RemainingScaleBytesNotEmptyException as e:
|
|
860
|
+
# Expected - SubnetInfo decode might raise this even on success
|
|
861
|
+
exception_raised = e
|
|
862
|
+
# Try to extract consumed bytes from exception message in various formats
|
|
863
|
+
exc_msg = str(e)
|
|
864
|
+
if "Current offset:" in exc_msg:
|
|
865
|
+
try:
|
|
866
|
+
# Parse offset from exception: "Decoding <SubnetInfo> - Current offset: 359 / length: 1560"
|
|
867
|
+
parts = (
|
|
868
|
+
exc_msg.split("Current offset:")[1].split("/")[0].strip()
|
|
869
|
+
)
|
|
870
|
+
consumed_bytes = int(parts)
|
|
871
|
+
logger.debug(
|
|
872
|
+
f"Extracted consumed bytes from 'Current offset:' pattern: {consumed_bytes}"
|
|
873
|
+
)
|
|
874
|
+
except (ValueError, IndexError) as parse_error:
|
|
875
|
+
logger.debug(
|
|
876
|
+
f"Could not parse offset from 'Current offset:' pattern: {parse_error}"
|
|
877
|
+
)
|
|
878
|
+
elif "needed:" in exc_msg and "/ total:" in exc_msg:
|
|
879
|
+
# Handle "Decoding <Bytes> - No more bytes available (needed: X / total: Y)"
|
|
880
|
+
# This usually means we ran out of bytes, but check if data.offset gives us a value
|
|
881
|
+
try:
|
|
882
|
+
if hasattr(single_obj, "data") and hasattr(
|
|
883
|
+
single_obj.data, "offset"
|
|
884
|
+
):
|
|
885
|
+
# The offset should tell us how many bytes were consumed before the error
|
|
886
|
+
potential_offset = single_obj.data.offset
|
|
887
|
+
# If offset is reasonable (less than remaining bytes), use it
|
|
888
|
+
if 0 < potential_offset <= len(subnet_bytes):
|
|
889
|
+
consumed_bytes = potential_offset
|
|
890
|
+
logger.debug(
|
|
891
|
+
f"Extracted consumed bytes from data.offset after 'needed/total' exception: {consumed_bytes}"
|
|
892
|
+
)
|
|
893
|
+
except Exception as offset_error:
|
|
894
|
+
logger.debug(f"Could not get offset from data: {offset_error}")
|
|
895
|
+
except Exception as e:
|
|
896
|
+
logger.error(f"Failed to decode subnet {i + 1}: {e}")
|
|
897
|
+
break
|
|
898
|
+
|
|
899
|
+
# Extract value even if exception was raised
|
|
900
|
+
# Check if value exists and is not None
|
|
901
|
+
has_value = hasattr(single_obj, "value") and single_obj.value is not None
|
|
902
|
+
|
|
903
|
+
if has_value:
|
|
904
|
+
decoded_subnets.append(single_obj.value)
|
|
905
|
+
|
|
906
|
+
# Get consumed bytes - try multiple methods
|
|
907
|
+
if consumed_bytes == 0:
|
|
908
|
+
# Try data.offset first
|
|
909
|
+
if hasattr(single_obj, "data") and hasattr(
|
|
910
|
+
single_obj.data, "offset"
|
|
911
|
+
):
|
|
912
|
+
potential_offset = single_obj.data.offset
|
|
913
|
+
# Only use if it's reasonable
|
|
914
|
+
if 0 < potential_offset <= len(subnet_bytes):
|
|
915
|
+
consumed_bytes = potential_offset
|
|
916
|
+
logger.debug(
|
|
917
|
+
f"Got consumed bytes from data.offset: {consumed_bytes}"
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
# Advance offset
|
|
921
|
+
if consumed_bytes > 0:
|
|
922
|
+
offset += consumed_bytes
|
|
923
|
+
logger.debug(
|
|
924
|
+
f"Subnet {i + 1} decoded successfully, consumed {consumed_bytes} bytes, new offset: {offset}"
|
|
925
|
+
)
|
|
926
|
+
else:
|
|
927
|
+
# Last resort: estimate based on remaining bytes
|
|
928
|
+
if i < vec_length - 1:
|
|
929
|
+
# Estimate size based on remaining bytes divided by remaining subnets
|
|
930
|
+
remaining_subnets = vec_length - i
|
|
931
|
+
estimated_size = len(subnet_bytes) // remaining_subnets
|
|
932
|
+
# Use minimum of estimated size or a reasonable maximum (500 bytes)
|
|
933
|
+
consumed_bytes = min(estimated_size, 500)
|
|
934
|
+
offset += consumed_bytes
|
|
935
|
+
logger.warning(
|
|
936
|
+
f"Subnet {i + 1} decoded but couldn't determine consumed bytes, using estimate: {consumed_bytes} bytes"
|
|
937
|
+
)
|
|
938
|
+
else:
|
|
939
|
+
logger.debug(
|
|
940
|
+
f"Last subnet {i + 1} decoded, no need to advance offset"
|
|
941
|
+
)
|
|
942
|
+
else:
|
|
943
|
+
# No value extracted - this subnet decoding failed
|
|
944
|
+
# If we have consumed_bytes from data.offset, we might still want to try skipping forward
|
|
945
|
+
if consumed_bytes > 0 and consumed_bytes <= len(subnet_bytes):
|
|
946
|
+
logger.warning(
|
|
947
|
+
f"Subnet {i + 1} decoding failed but got consumed_bytes={consumed_bytes}, advancing anyway"
|
|
948
|
+
)
|
|
949
|
+
offset += consumed_bytes
|
|
950
|
+
else:
|
|
951
|
+
logger.error(f"No value extracted for subnet {i + 1}")
|
|
952
|
+
if exception_raised:
|
|
953
|
+
logger.debug(f"Exception was: {exception_raised}")
|
|
954
|
+
# Try to estimate and continue if not the last subnet
|
|
955
|
+
if i < vec_length - 1:
|
|
956
|
+
remaining_subnets = vec_length - i
|
|
957
|
+
estimated_size = len(subnet_bytes) // remaining_subnets
|
|
958
|
+
estimated_size = min(estimated_size, 500)
|
|
959
|
+
logger.warning(
|
|
960
|
+
f"Attempting to continue with estimated size: {estimated_size} bytes"
|
|
961
|
+
)
|
|
962
|
+
offset += estimated_size
|
|
963
|
+
else:
|
|
964
|
+
break
|
|
965
|
+
|
|
966
|
+
if decoded_subnets:
|
|
967
|
+
# Check if we decoded all expected subnets
|
|
968
|
+
if len(decoded_subnets) < vec_length:
|
|
969
|
+
logger.warning(
|
|
970
|
+
f"Manual iteration only decoded {len(decoded_subnets)}/{vec_length} subnets. "
|
|
971
|
+
"Returning empty list to trigger fallback method."
|
|
972
|
+
)
|
|
973
|
+
return []
|
|
974
|
+
logger.info(
|
|
975
|
+
f"Successfully decoded {len(decoded_subnets)} subnet(s) using manual iteration"
|
|
976
|
+
)
|
|
977
|
+
return decoded_subnets
|
|
978
|
+
else:
|
|
979
|
+
logger.error("Manual iteration decoded 0 subnets")
|
|
980
|
+
return []
|
|
981
|
+
|
|
982
|
+
except Exception as e:
|
|
983
|
+
logger.error(f"Unexpected error decoding Vec<SubnetInfo>: {e}")
|
|
984
|
+
return []
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def _manual_decode_vec_subnet_node_info(
|
|
988
|
+
normalized: bytes, config, vec_length: int
|
|
989
|
+
) -> list[dict]:
|
|
990
|
+
"""
|
|
991
|
+
Manually decode Vec<SubnetNodeInfo> by iterating through each element.
|
|
992
|
+
|
|
993
|
+
This is used as a fallback when the direct Vec decode only returns partial results.
|
|
994
|
+
"""
|
|
995
|
+
try:
|
|
996
|
+
# Read Vec length from compact encoding (don't trust the passed vec_length parameter)
|
|
997
|
+
vec_length_byte = normalized[0]
|
|
998
|
+
actual_vec_length = vec_length # Default to passed value
|
|
999
|
+
|
|
1000
|
+
# Decode actual Vec length from compact encoding
|
|
1001
|
+
if (vec_length_byte & 0x03) == 0x00:
|
|
1002
|
+
actual_vec_length = vec_length_byte >> 2
|
|
1003
|
+
offset = 1
|
|
1004
|
+
elif (vec_length_byte & 0x03) == 0x01:
|
|
1005
|
+
if len(normalized) >= 2:
|
|
1006
|
+
actual_vec_length = (
|
|
1007
|
+
(vec_length_byte >> 2) | (normalized[1] << 6)
|
|
1008
|
+
) & 0x3FFF
|
|
1009
|
+
offset = 2
|
|
1010
|
+
else:
|
|
1011
|
+
offset = 2
|
|
1012
|
+
elif (vec_length_byte & 0x03) == 0x02:
|
|
1013
|
+
if len(normalized) >= 4:
|
|
1014
|
+
actual_vec_length = (
|
|
1015
|
+
((vec_length_byte >> 2) & 0x3F)
|
|
1016
|
+
| (normalized[1] << 6)
|
|
1017
|
+
| (normalized[2] << 14)
|
|
1018
|
+
| (normalized[3] << 22)
|
|
1019
|
+
) & 0x3FFFFFFF
|
|
1020
|
+
offset = 4
|
|
1021
|
+
else:
|
|
1022
|
+
offset = 4
|
|
1023
|
+
else:
|
|
1024
|
+
offset = 1
|
|
1025
|
+
|
|
1026
|
+
decoded_nodes = []
|
|
1027
|
+
|
|
1028
|
+
# Decode each SubnetNodeInfo individually using the actual Vec length
|
|
1029
|
+
for i in range(actual_vec_length):
|
|
1030
|
+
if offset >= len(normalized):
|
|
1031
|
+
logger.warning(
|
|
1032
|
+
f"Ran out of data while decoding node {i + 1}/{actual_vec_length} at offset {offset}"
|
|
1033
|
+
)
|
|
1034
|
+
break
|
|
1035
|
+
|
|
1036
|
+
node_bytes = normalized[offset:]
|
|
1037
|
+
|
|
1038
|
+
single_obj = config.create_scale_object("SubnetNodeInfo")
|
|
1039
|
+
consumed_bytes = 0
|
|
1040
|
+
|
|
1041
|
+
try:
|
|
1042
|
+
single_obj.decode(ScaleBytes(node_bytes))
|
|
1043
|
+
# Success - no exception
|
|
1044
|
+
except RemainingScaleBytesNotEmptyException as e:
|
|
1045
|
+
# Expected - SubnetNodeInfo decode might raise this even on success
|
|
1046
|
+
exc_msg = str(e)
|
|
1047
|
+
# Try to extract consumed bytes from exception message in various formats
|
|
1048
|
+
if "Current offset:" in exc_msg:
|
|
1049
|
+
try:
|
|
1050
|
+
# Parse offset from exception: "Decoding <SubnetNodeInfo> - Current offset: 359 / length: 1560"
|
|
1051
|
+
parts = (
|
|
1052
|
+
exc_msg.split("Current offset:")[1].split("/")[0].strip()
|
|
1053
|
+
)
|
|
1054
|
+
consumed_bytes = int(parts)
|
|
1055
|
+
except (ValueError, IndexError):
|
|
1056
|
+
pass
|
|
1057
|
+
elif "needed:" in exc_msg and "/ total:" in exc_msg:
|
|
1058
|
+
# Handle "Decoding <Bytes> - No more bytes available (needed: X / total: Y)"
|
|
1059
|
+
# This usually means we ran out of bytes, but check if data.offset gives us a value
|
|
1060
|
+
try:
|
|
1061
|
+
if hasattr(single_obj, "data") and hasattr(
|
|
1062
|
+
single_obj.data, "offset"
|
|
1063
|
+
):
|
|
1064
|
+
potential_offset = single_obj.data.offset
|
|
1065
|
+
if 0 < potential_offset <= len(node_bytes):
|
|
1066
|
+
consumed_bytes = potential_offset
|
|
1067
|
+
except Exception:
|
|
1068
|
+
pass
|
|
1069
|
+
except Exception as e:
|
|
1070
|
+
logger.error(f"Failed to decode node {i + 1}: {e}")
|
|
1071
|
+
break
|
|
1072
|
+
|
|
1073
|
+
# Extract value even if exception was raised
|
|
1074
|
+
has_value = hasattr(single_obj, "value") and single_obj.value is not None
|
|
1075
|
+
|
|
1076
|
+
if has_value:
|
|
1077
|
+
decoded_nodes.append(single_obj.value)
|
|
1078
|
+
|
|
1079
|
+
# Get consumed bytes - try multiple methods
|
|
1080
|
+
if consumed_bytes == 0:
|
|
1081
|
+
# Try data.offset first
|
|
1082
|
+
if hasattr(single_obj, "data") and hasattr(
|
|
1083
|
+
single_obj.data, "offset"
|
|
1084
|
+
):
|
|
1085
|
+
potential_offset = single_obj.data.offset
|
|
1086
|
+
# Only use if it's reasonable
|
|
1087
|
+
if 0 < potential_offset <= len(node_bytes):
|
|
1088
|
+
consumed_bytes = potential_offset
|
|
1089
|
+
|
|
1090
|
+
# Advance offset
|
|
1091
|
+
if consumed_bytes > 0:
|
|
1092
|
+
offset += consumed_bytes
|
|
1093
|
+
else:
|
|
1094
|
+
# Last resort: estimate based on remaining bytes
|
|
1095
|
+
if i < actual_vec_length - 1:
|
|
1096
|
+
# Estimate size based on remaining bytes divided by remaining nodes
|
|
1097
|
+
remaining_nodes = actual_vec_length - i
|
|
1098
|
+
estimated_size = len(node_bytes) // remaining_nodes
|
|
1099
|
+
# Use minimum of estimated size or a reasonable maximum (500 bytes)
|
|
1100
|
+
consumed_bytes = min(estimated_size, 500)
|
|
1101
|
+
offset += consumed_bytes
|
|
1102
|
+
else:
|
|
1103
|
+
if i < actual_vec_length - 1:
|
|
1104
|
+
remaining_nodes = actual_vec_length - i
|
|
1105
|
+
estimated_size = len(node_bytes) // remaining_nodes
|
|
1106
|
+
estimated_size = min(estimated_size, 500)
|
|
1107
|
+
offset += estimated_size
|
|
1108
|
+
else:
|
|
1109
|
+
break
|
|
1110
|
+
|
|
1111
|
+
if decoded_nodes:
|
|
1112
|
+
logger.info(
|
|
1113
|
+
f"Manually decoded {len(decoded_nodes)}/{actual_vec_length} node(s)"
|
|
1114
|
+
)
|
|
1115
|
+
return decoded_nodes
|
|
1116
|
+
else:
|
|
1117
|
+
logger.error("Manual decode decoded 0 nodes")
|
|
1118
|
+
return []
|
|
1119
|
+
|
|
1120
|
+
except Exception as e:
|
|
1121
|
+
logger.error(f"Unexpected error in manual Vec<SubnetNodeInfo> decode: {e}")
|
|
1122
|
+
return []
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
def decode_option_subnet_node_info(data: bytes) -> dict:
|
|
1126
|
+
"""
|
|
1127
|
+
Decode Option<SubnetNodeInfo> from SCALE bytes.
|
|
1128
|
+
|
|
1129
|
+
Args:
|
|
1130
|
+
data: SCALE-encoded bytes
|
|
1131
|
+
|
|
1132
|
+
Returns:
|
|
1133
|
+
Decoded SubnetNodeInfo dict or None if not found
|
|
1134
|
+
"""
|
|
1135
|
+
normalized = _normalize_scale_input(data)
|
|
1136
|
+
if not normalized:
|
|
1137
|
+
return None
|
|
1138
|
+
|
|
1139
|
+
config = get_rpc_runtime_config()
|
|
1140
|
+
|
|
1141
|
+
# Check first byte to see if Option is Some (0x01) or None (0x00)
|
|
1142
|
+
if len(normalized) == 0:
|
|
1143
|
+
return None
|
|
1144
|
+
|
|
1145
|
+
option_byte = normalized[0]
|
|
1146
|
+
if option_byte == 0x00:
|
|
1147
|
+
# Option::None
|
|
1148
|
+
return None
|
|
1149
|
+
elif option_byte == 0x01:
|
|
1150
|
+
# Option::Some - decode the SubnetNodeInfo directly (skip discriminant)
|
|
1151
|
+
info_bytes = normalized[1:]
|
|
1152
|
+
|
|
1153
|
+
info_obj = config.create_scale_object("SubnetNodeInfo")
|
|
1154
|
+
try:
|
|
1155
|
+
info_obj.decode(ScaleBytes(info_bytes))
|
|
1156
|
+
except RemainingScaleBytesNotEmptyException:
|
|
1157
|
+
# Expected for some registry mismatches; value is still populated
|
|
1158
|
+
pass
|
|
1159
|
+
except Exception as e:
|
|
1160
|
+
logger.error(f"Failed to decode SubnetNodeInfo in Option: {e}")
|
|
1161
|
+
return None
|
|
1162
|
+
|
|
1163
|
+
if hasattr(info_obj, "value") and info_obj.value:
|
|
1164
|
+
return info_obj.value
|
|
1165
|
+
|
|
1166
|
+
return None
|
|
1167
|
+
|
|
1168
|
+
# Unexpected discriminant
|
|
1169
|
+
logger.warning(f"Unknown Option<SubnetNodeInfo> discriminant byte: {option_byte}")
|
|
1170
|
+
return None
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
def decode_vec_subnet_node_info(data: bytes) -> list[dict]:
|
|
1174
|
+
"""
|
|
1175
|
+
Decode Vec<SubnetNodeInfo> from SCALE bytes.
|
|
1176
|
+
|
|
1177
|
+
This mirrors the more defensive logic used for Vec<SubnetInfo> and
|
|
1178
|
+
tolerates RemainingScaleBytesNotEmptyException by extracting already
|
|
1179
|
+
decoded values from the Vec object.
|
|
1180
|
+
|
|
1181
|
+
Args:
|
|
1182
|
+
data: SCALE-encoded bytes (list of ints or bytes object)
|
|
1183
|
+
|
|
1184
|
+
Returns:
|
|
1185
|
+
List of decoded SubnetNodeInfo dicts
|
|
1186
|
+
"""
|
|
1187
|
+
normalized = _normalize_scale_input(data)
|
|
1188
|
+
if not normalized:
|
|
1189
|
+
return []
|
|
1190
|
+
|
|
1191
|
+
config = get_rpc_runtime_config()
|
|
1192
|
+
|
|
1193
|
+
try:
|
|
1194
|
+
vec_obj = config.create_scale_object("Vec<SubnetNodeInfo>")
|
|
1195
|
+
|
|
1196
|
+
exception_occurred = False
|
|
1197
|
+
exception_msg = None
|
|
1198
|
+
try:
|
|
1199
|
+
vec_obj.decode(ScaleBytes(normalized))
|
|
1200
|
+
logger.debug("Vec<SubnetNodeInfo> decoded without exception")
|
|
1201
|
+
except RemainingScaleBytesNotEmptyException as e:
|
|
1202
|
+
# Expected in some cases; values are still populated on the object
|
|
1203
|
+
exception_occurred = True
|
|
1204
|
+
exception_msg = str(e)
|
|
1205
|
+
logger.debug(
|
|
1206
|
+
f"RemainingScaleBytesNotEmptyException during Vec<SubnetNodeInfo> decode: {exception_msg}"
|
|
1207
|
+
)
|
|
1208
|
+
except Exception as e:
|
|
1209
|
+
logger.warning(
|
|
1210
|
+
f"Exception during Vec<SubnetNodeInfo> decode: {e}, trying to extract partial results"
|
|
1211
|
+
)
|
|
1212
|
+
exception_occurred = True
|
|
1213
|
+
exception_msg = str(e)
|
|
1214
|
+
|
|
1215
|
+
decoded_list = None
|
|
1216
|
+
|
|
1217
|
+
# Method 1: direct .value
|
|
1218
|
+
if hasattr(vec_obj, "value") and vec_obj.value is not None:
|
|
1219
|
+
decoded_list = vec_obj.value
|
|
1220
|
+
|
|
1221
|
+
# Method 2: value_list
|
|
1222
|
+
if (
|
|
1223
|
+
(
|
|
1224
|
+
decoded_list is None
|
|
1225
|
+
or (isinstance(decoded_list, list) and len(decoded_list) == 0)
|
|
1226
|
+
)
|
|
1227
|
+
and hasattr(vec_obj, "value_list")
|
|
1228
|
+
and vec_obj.value_list
|
|
1229
|
+
):
|
|
1230
|
+
decoded_list = vec_obj.value_list
|
|
1231
|
+
|
|
1232
|
+
# Method 3: internal elements attributes
|
|
1233
|
+
if decoded_list is None or (
|
|
1234
|
+
isinstance(decoded_list, list) and len(decoded_list) == 0
|
|
1235
|
+
):
|
|
1236
|
+
elements = None
|
|
1237
|
+
if hasattr(vec_obj, "_elements"):
|
|
1238
|
+
elements = vec_obj._elements
|
|
1239
|
+
elif hasattr(vec_obj, "elements"):
|
|
1240
|
+
elements = vec_obj.elements
|
|
1241
|
+
|
|
1242
|
+
if elements:
|
|
1243
|
+
try:
|
|
1244
|
+
decoded_list = [el.value for el in elements]
|
|
1245
|
+
except Exception:
|
|
1246
|
+
decoded_list = elements
|
|
1247
|
+
|
|
1248
|
+
if decoded_list is None:
|
|
1249
|
+
if exception_occurred:
|
|
1250
|
+
logger.warning(
|
|
1251
|
+
f"Could not extract SubnetNodeInfo list after Vec decode error: {exception_msg}"
|
|
1252
|
+
)
|
|
1253
|
+
return []
|
|
1254
|
+
|
|
1255
|
+
# Ensure we always return a plain list of dicts
|
|
1256
|
+
result: list[dict] = []
|
|
1257
|
+
for item in decoded_list:
|
|
1258
|
+
if isinstance(item, dict):
|
|
1259
|
+
result.append(item)
|
|
1260
|
+
elif hasattr(item, "value") and isinstance(item.value, dict):
|
|
1261
|
+
result.append(item.value)
|
|
1262
|
+
else:
|
|
1263
|
+
# Fallback: try model_dump if it's a Pydantic-like object
|
|
1264
|
+
try:
|
|
1265
|
+
dumped = item.model_dump()
|
|
1266
|
+
if isinstance(dumped, dict):
|
|
1267
|
+
result.append(dumped)
|
|
1268
|
+
except Exception:
|
|
1269
|
+
logger.debug(
|
|
1270
|
+
f"Skipping non-dict Vec<SubnetNodeInfo> element: {item}"
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
# If we got a partial decode (exception occurred or we have large data but few results),
|
|
1274
|
+
# try manual iteration to decode all elements
|
|
1275
|
+
should_try_manual = False
|
|
1276
|
+
vec_length = len(result) # Default to what we have
|
|
1277
|
+
|
|
1278
|
+
# Check if we should try manual decode:
|
|
1279
|
+
# 1. Exception occurred during decode, OR
|
|
1280
|
+
# 2. We have significant data (>100 bytes) but only decoded 1-2 items (suggests partial decode)
|
|
1281
|
+
if exception_occurred or (len(normalized) > 100 and len(result) <= 2):
|
|
1282
|
+
should_try_manual = True
|
|
1283
|
+
|
|
1284
|
+
# Try to read actual Vec length from compact encoding
|
|
1285
|
+
try:
|
|
1286
|
+
vec_length_byte = normalized[0]
|
|
1287
|
+
# Compact encoding: first 2 bits determine format
|
|
1288
|
+
if (vec_length_byte & 0x03) == 0x00:
|
|
1289
|
+
vec_length = vec_length_byte >> 2
|
|
1290
|
+
elif (vec_length_byte & 0x03) == 0x01:
|
|
1291
|
+
if len(normalized) >= 2:
|
|
1292
|
+
vec_length = (
|
|
1293
|
+
(vec_length_byte >> 2) | (normalized[1] << 6)
|
|
1294
|
+
) & 0x3FFF
|
|
1295
|
+
elif (vec_length_byte & 0x03) == 0x02:
|
|
1296
|
+
if len(normalized) >= 4:
|
|
1297
|
+
vec_length = (
|
|
1298
|
+
((vec_length_byte >> 2) & 0x3F)
|
|
1299
|
+
| (normalized[1] << 6)
|
|
1300
|
+
| (normalized[2] << 14)
|
|
1301
|
+
| (normalized[3] << 22)
|
|
1302
|
+
) & 0x3FFFFFFF
|
|
1303
|
+
except Exception as e:
|
|
1304
|
+
logger.debug(f"Could not read Vec length: {e}")
|
|
1305
|
+
# Estimate vec_length from data size (rough estimate: ~200-500 bytes per node)
|
|
1306
|
+
estimated_length = max(len(result), len(normalized) // 300)
|
|
1307
|
+
vec_length = min(estimated_length, 100) # Cap at reasonable limit
|
|
1308
|
+
|
|
1309
|
+
if should_try_manual and vec_length > len(result):
|
|
1310
|
+
manual_result = _manual_decode_vec_subnet_node_info(
|
|
1311
|
+
normalized, config, vec_length
|
|
1312
|
+
)
|
|
1313
|
+
if manual_result and len(manual_result) >= len(result):
|
|
1314
|
+
return manual_result
|
|
1315
|
+
|
|
1316
|
+
return result
|
|
1317
|
+
|
|
1318
|
+
except Exception as e:
|
|
1319
|
+
logger.error(f"Failed to decode Vec<SubnetNodeInfo>: {e}")
|
|
1320
|
+
return []
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
def decode_vec_subnet_node_stake_info(data: bytes) -> list[dict]:
|
|
1324
|
+
"""
|
|
1325
|
+
Decode Vec<SubnetNodeStakeInfo> from SCALE bytes.
|
|
1326
|
+
|
|
1327
|
+
Args:
|
|
1328
|
+
data: SCALE-encoded bytes
|
|
1329
|
+
|
|
1330
|
+
Returns:
|
|
1331
|
+
List of decoded SubnetNodeStakeInfo dicts
|
|
1332
|
+
"""
|
|
1333
|
+
decoded = _decode_scale_object(data, "Vec<SubnetNodeStakeInfo>")
|
|
1334
|
+
return decoded or []
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
def decode_vec_node_stake_info(data: bytes) -> list[dict]:
|
|
1338
|
+
"""
|
|
1339
|
+
Decode Vec<NodeStakeInfo> from SCALE bytes.
|
|
1340
|
+
|
|
1341
|
+
Validator stake RPCs return NodeStakeInfo, which omits the hotkey field
|
|
1342
|
+
present in legacy SubnetNodeStakeInfo responses.
|
|
1343
|
+
"""
|
|
1344
|
+
decoded = _decode_scale_object(data, "Vec<NodeStakeInfo>")
|
|
1345
|
+
return decoded or []
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
def decode_vec_delegate_stake_info(data: bytes) -> list[dict]:
|
|
1349
|
+
"""
|
|
1350
|
+
Decode Vec<DelegateStakeInfo> from SCALE bytes.
|
|
1351
|
+
|
|
1352
|
+
Args:
|
|
1353
|
+
data: SCALE-encoded bytes
|
|
1354
|
+
|
|
1355
|
+
Returns:
|
|
1356
|
+
List of decoded DelegateStakeInfo dicts
|
|
1357
|
+
"""
|
|
1358
|
+
decoded = _decode_scale_object(data, "Vec<DelegateStakeInfo>")
|
|
1359
|
+
return decoded or []
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
def decode_vec_node_delegate_stake_info(data: bytes) -> list[dict]:
|
|
1363
|
+
"""
|
|
1364
|
+
Decode Vec<NodeDelegateStakeInfo> from SCALE bytes.
|
|
1365
|
+
|
|
1366
|
+
Args:
|
|
1367
|
+
data: SCALE-encoded bytes
|
|
1368
|
+
|
|
1369
|
+
Returns:
|
|
1370
|
+
List of decoded NodeDelegateStakeInfo dicts
|
|
1371
|
+
"""
|
|
1372
|
+
decoded = _decode_scale_object(data, "Vec<NodeDelegateStakeInfo>")
|
|
1373
|
+
return decoded or []
|