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,2218 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Subnet management extrinsics for HTCLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ipaddress
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import base58
|
|
9
|
+
from substrateinterface import Keypair, SubstrateInterface
|
|
10
|
+
from substrateinterface.exceptions import SubstrateRequestException
|
|
11
|
+
|
|
12
|
+
from ...models.enums.enum_types import KeyType
|
|
13
|
+
from ...models.requests.subnet import (
|
|
14
|
+
SubnetActivateRequest,
|
|
15
|
+
SubnetConfigUpdateRequest,
|
|
16
|
+
SubnetOwnershipAcceptRequest,
|
|
17
|
+
SubnetOwnershipTransferRequest,
|
|
18
|
+
SubnetRegisterRequest,
|
|
19
|
+
SubnetRemoveRequest,
|
|
20
|
+
SubnetUpdateRequest,
|
|
21
|
+
)
|
|
22
|
+
from ...models.responses.subnet import SubnetInfo, SubnetRegisterResponse
|
|
23
|
+
from ...utils.blockchain.patches import patch_btreemap_accountid_u32_encoder
|
|
24
|
+
from ...utils.blockchain.peer_id import encode_peer_id
|
|
25
|
+
from ...utils.blockchain.validation import validate_address
|
|
26
|
+
from ...utils.logging import get_logger
|
|
27
|
+
from .base import BaseExtrinsicClient
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Simple response wrapper for methods that don't have specific response models
|
|
31
|
+
class SimpleExtrinsicResponse:
|
|
32
|
+
"""Simple wrapper for extrinsic responses."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, response_dict: dict):
|
|
35
|
+
self.success = response_dict.get("success", False)
|
|
36
|
+
self.error = response_dict.get("error")
|
|
37
|
+
self.transaction_hash = response_dict.get("extrinsic_hash")
|
|
38
|
+
self.block_number = response_dict.get("block_number")
|
|
39
|
+
self.block_hash = response_dict.get("block_hash")
|
|
40
|
+
self.message = response_dict.get("message")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
logger = get_logger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _encode_unsigned_varint(value: int) -> bytes:
|
|
47
|
+
if value < 0:
|
|
48
|
+
raise ValueError("varint value cannot be negative")
|
|
49
|
+
|
|
50
|
+
encoded = bytearray()
|
|
51
|
+
while value >= 0x80:
|
|
52
|
+
encoded.append((value & 0x7F) | 0x80)
|
|
53
|
+
value >>= 7
|
|
54
|
+
encoded.append(value)
|
|
55
|
+
return bytes(encoded)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _encode_multiaddr(multiaddr: str) -> bytes:
|
|
59
|
+
"""Encode a supported multiaddr string into libp2p multiaddr bytes."""
|
|
60
|
+
protocols = {
|
|
61
|
+
"ip4": 4,
|
|
62
|
+
"tcp": 6,
|
|
63
|
+
"udp": 273,
|
|
64
|
+
"ip6": 41,
|
|
65
|
+
"dns": 53,
|
|
66
|
+
"dns4": 54,
|
|
67
|
+
"dns6": 55,
|
|
68
|
+
"dnsaddr": 56,
|
|
69
|
+
"p2p": 421,
|
|
70
|
+
}
|
|
71
|
+
parts = [part for part in multiaddr.split("/") if part]
|
|
72
|
+
encoded = bytearray()
|
|
73
|
+
index = 0
|
|
74
|
+
|
|
75
|
+
while index < len(parts):
|
|
76
|
+
proto = parts[index].lower()
|
|
77
|
+
if proto not in protocols:
|
|
78
|
+
raise ValueError(f"Unsupported multiaddr protocol: {proto}")
|
|
79
|
+
if index + 1 >= len(parts):
|
|
80
|
+
raise ValueError(f"Missing value for multiaddr protocol: {proto}")
|
|
81
|
+
|
|
82
|
+
value = parts[index + 1]
|
|
83
|
+
encoded += _encode_unsigned_varint(protocols[proto])
|
|
84
|
+
|
|
85
|
+
if proto == "ip4":
|
|
86
|
+
encoded += ipaddress.IPv4Address(value).packed
|
|
87
|
+
elif proto == "ip6":
|
|
88
|
+
encoded += ipaddress.IPv6Address(value).packed
|
|
89
|
+
elif proto in {"tcp", "udp"}:
|
|
90
|
+
port = int(value)
|
|
91
|
+
if not 0 <= port <= 65535:
|
|
92
|
+
raise ValueError(f"Invalid {proto} port: {port}")
|
|
93
|
+
encoded += port.to_bytes(2, byteorder="big", signed=False)
|
|
94
|
+
elif proto == "p2p":
|
|
95
|
+
peer_bytes = base58.b58decode(value)
|
|
96
|
+
encoded += _encode_unsigned_varint(len(peer_bytes))
|
|
97
|
+
encoded += peer_bytes
|
|
98
|
+
else:
|
|
99
|
+
value_bytes = value.encode("utf-8")
|
|
100
|
+
encoded += _encode_unsigned_varint(len(value_bytes))
|
|
101
|
+
encoded += value_bytes
|
|
102
|
+
|
|
103
|
+
index += 2
|
|
104
|
+
|
|
105
|
+
return bytes(encoded)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class SubnetExtrinsics(BaseExtrinsicClient):
|
|
109
|
+
"""Client for subnet-related extrinsics."""
|
|
110
|
+
|
|
111
|
+
def __init__(self, substrate: Optional[SubstrateInterface] = None):
|
|
112
|
+
"""Initialize the subnet client."""
|
|
113
|
+
super().__init__(substrate)
|
|
114
|
+
|
|
115
|
+
# ============================================================================
|
|
116
|
+
# SUBNET REGISTRATION
|
|
117
|
+
# ============================================================================
|
|
118
|
+
|
|
119
|
+
def register_subnet(
|
|
120
|
+
self, request: SubnetRegisterRequest, keypair: Keypair
|
|
121
|
+
) -> SubnetRegisterResponse:
|
|
122
|
+
"""
|
|
123
|
+
Register a new subnet on the network.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
request: Subnet registration request
|
|
127
|
+
keypair: Keypair for signing the transaction
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
SubnetRegisterResponse with registration result
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
logger.info(f"Registering subnet: {request.name}")
|
|
134
|
+
|
|
135
|
+
# Validate inputs
|
|
136
|
+
self._validate_registration_request(request)
|
|
137
|
+
|
|
138
|
+
# Get current registration cost
|
|
139
|
+
current_cost = self._get_current_registration_cost()
|
|
140
|
+
logger.info(f"Current registration cost: {current_cost}")
|
|
141
|
+
|
|
142
|
+
# Ensure max_cost covers the current cost
|
|
143
|
+
if request.max_cost < current_cost:
|
|
144
|
+
return SubnetRegisterResponse(
|
|
145
|
+
success=False,
|
|
146
|
+
error=f"Max cost {request.max_cost} is less than current cost {current_cost}",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Prepare subnet data for the extrinsic
|
|
150
|
+
subnet_data = self._prepare_subnet_data(request)
|
|
151
|
+
|
|
152
|
+
# Compose the extrinsic call
|
|
153
|
+
# Note: register_subnet does NOT take a hotkey parameter
|
|
154
|
+
# The owner is derived from the origin (signer)
|
|
155
|
+
logger.info("Submitting subnet registration extrinsic...")
|
|
156
|
+
with patch_btreemap_accountid_u32_encoder():
|
|
157
|
+
call = self.substrate.compose_call(
|
|
158
|
+
call_module="Network",
|
|
159
|
+
call_function="register_subnet",
|
|
160
|
+
call_params={
|
|
161
|
+
"max_cost": request.max_cost,
|
|
162
|
+
"subnet_data": subnet_data,
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Submit the extrinsic using base class method with retry logic
|
|
167
|
+
result = self._submit_extrinsic(call, keypair)
|
|
168
|
+
|
|
169
|
+
if result["success"]:
|
|
170
|
+
# Debug: print result keys
|
|
171
|
+
logger.info(f"Registration result keys: {result.keys()}")
|
|
172
|
+
logger.info(f"Events in result: {len(result.get('events', []))}")
|
|
173
|
+
|
|
174
|
+
# Extract event data
|
|
175
|
+
event_data = self._extract_registration_event_from_result(result)
|
|
176
|
+
logger.info(f"Extracted event data: {event_data}")
|
|
177
|
+
|
|
178
|
+
return SubnetRegisterResponse(
|
|
179
|
+
success=True,
|
|
180
|
+
subnet_id=event_data.get("subnet_id"),
|
|
181
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
182
|
+
block_hash=result.get("block_hash"),
|
|
183
|
+
cost_paid=current_cost,
|
|
184
|
+
owner=event_data.get("owner"),
|
|
185
|
+
name=event_data.get("name"),
|
|
186
|
+
block_number=result.get("block_number"),
|
|
187
|
+
epoch=self._get_current_epoch(),
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
error_msg = result.get("error", "Unknown error")
|
|
191
|
+
return SubnetRegisterResponse(success=False, error=error_msg)
|
|
192
|
+
|
|
193
|
+
except SubstrateRequestException as e:
|
|
194
|
+
logger.error(f"Substrate request error: {e}")
|
|
195
|
+
logger.debug(
|
|
196
|
+
f"SubstrateRequestException details - type: {type(e)}, message: {str(e)}"
|
|
197
|
+
)
|
|
198
|
+
return SubnetRegisterResponse(
|
|
199
|
+
success=False, error=f"Network error: {str(e)}"
|
|
200
|
+
)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.error(f"Unexpected error during subnet registration: {e}")
|
|
203
|
+
logger.debug(
|
|
204
|
+
f"Exception details - type: {type(e)}, message: {str(e)}", exc_info=True
|
|
205
|
+
)
|
|
206
|
+
return SubnetRegisterResponse(
|
|
207
|
+
success=False, error=f"Unexpected error: {str(e)}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# ============================================================================
|
|
211
|
+
# SUBNET ACTIVATION/DEACTIVATION
|
|
212
|
+
# ============================================================================
|
|
213
|
+
|
|
214
|
+
def activate_subnet(
|
|
215
|
+
self, request: SubnetActivateRequest, keypair: Keypair
|
|
216
|
+
) -> SimpleExtrinsicResponse:
|
|
217
|
+
"""
|
|
218
|
+
Activate a subnet.
|
|
219
|
+
|
|
220
|
+
This extrinsic can result in two different outcomes:
|
|
221
|
+
1. SubnetActivated event - subnet successfully activated
|
|
222
|
+
2. SubnetDeactivated event - subnet was removed (failed to meet activation criteria)
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
request: Subnet activation request
|
|
226
|
+
keypair: Keypair for signing the transaction
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
SimpleExtrinsicResponse with operation result including actual outcome
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
logger.info(f"Activating subnet: {request.subnet_id}")
|
|
233
|
+
|
|
234
|
+
call = self.substrate.compose_call(
|
|
235
|
+
call_module="Network",
|
|
236
|
+
call_function="activate_subnet",
|
|
237
|
+
call_params={"subnet_id": request.subnet_id},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
response = self._submit_extrinsic(call, keypair)
|
|
241
|
+
|
|
242
|
+
if response["success"]:
|
|
243
|
+
events = response.get("events", [])
|
|
244
|
+
|
|
245
|
+
# Check for SubnetActivated event (success case)
|
|
246
|
+
activated_event = self._extract_event_by_type(
|
|
247
|
+
events, "Network", "SubnetActivated"
|
|
248
|
+
)
|
|
249
|
+
if activated_event:
|
|
250
|
+
response["message"] = (
|
|
251
|
+
f"Subnet {request.subnet_id} activated successfully"
|
|
252
|
+
)
|
|
253
|
+
response["actual_result"] = "activated"
|
|
254
|
+
return SimpleExtrinsicResponse(response)
|
|
255
|
+
|
|
256
|
+
# Check for SubnetDeactivated event (removal case)
|
|
257
|
+
deactivated_event = self._extract_event_by_type(
|
|
258
|
+
events, "Network", "SubnetDeactivated"
|
|
259
|
+
)
|
|
260
|
+
if deactivated_event:
|
|
261
|
+
# Extract removal reason from event attributes
|
|
262
|
+
reason = self._extract_attribute_value(
|
|
263
|
+
deactivated_event, 1, "Unknown"
|
|
264
|
+
)
|
|
265
|
+
reason_name = self._get_removal_reason_name(reason)
|
|
266
|
+
reason_message = self._get_subnet_removal_reason_message(
|
|
267
|
+
reason_name
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Mark as not successful since subnet was removed, not activated
|
|
271
|
+
response["success"] = False
|
|
272
|
+
response["actual_result"] = "removed"
|
|
273
|
+
response["removal_reason"] = reason_name
|
|
274
|
+
response["message"] = (
|
|
275
|
+
f"Subnet {request.subnet_id} was removed: {reason_message}"
|
|
276
|
+
)
|
|
277
|
+
response["error"] = f"SubnetDeactivated: {reason_message}"
|
|
278
|
+
|
|
279
|
+
logger.warning(
|
|
280
|
+
f"Subnet {request.subnet_id} was removed instead of activated. "
|
|
281
|
+
f"Reason: {reason_name}"
|
|
282
|
+
)
|
|
283
|
+
return SimpleExtrinsicResponse(response)
|
|
284
|
+
|
|
285
|
+
# Fallback: Transaction succeeded but we couldn't identify the outcome from events
|
|
286
|
+
# This shouldn't happen normally, but handle it gracefully
|
|
287
|
+
response["message"] = f"Subnet {request.subnet_id} activation completed"
|
|
288
|
+
response["actual_result"] = "unknown"
|
|
289
|
+
logger.warning(
|
|
290
|
+
f"Subnet {request.subnet_id} activation completed but could not "
|
|
291
|
+
"determine actual outcome from events"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return SimpleExtrinsicResponse(response)
|
|
295
|
+
|
|
296
|
+
except Exception as e:
|
|
297
|
+
logger.error(f"Error activating subnet: {e}")
|
|
298
|
+
return SimpleExtrinsicResponse({"success": False, "error": str(e)})
|
|
299
|
+
|
|
300
|
+
def _get_removal_reason_name(self, reason: Any) -> str:
|
|
301
|
+
"""
|
|
302
|
+
Extract the removal reason name from event attribute.
|
|
303
|
+
|
|
304
|
+
The reason can be a string, dict with 'type' key, or other format.
|
|
305
|
+
"""
|
|
306
|
+
if isinstance(reason, str):
|
|
307
|
+
return reason
|
|
308
|
+
if isinstance(reason, dict):
|
|
309
|
+
# Handle enum format: {'type': 'EnactmentPeriod'} or {'__variant__': 'EnactmentPeriod'}
|
|
310
|
+
return (
|
|
311
|
+
reason.get("type")
|
|
312
|
+
or reason.get("__variant__")
|
|
313
|
+
or reason.get("name", str(reason))
|
|
314
|
+
)
|
|
315
|
+
return str(reason)
|
|
316
|
+
|
|
317
|
+
def _get_subnet_removal_reason_message(self, reason: str) -> str:
|
|
318
|
+
"""
|
|
319
|
+
Convert SubnetRemovalReason enum to user-friendly message.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
reason: The removal reason enum variant name
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
User-friendly explanation of the removal reason
|
|
326
|
+
"""
|
|
327
|
+
REASONS = {
|
|
328
|
+
"MinReputation": "Minimum reputation requirements not met",
|
|
329
|
+
"MinSubnetNodes": "Minimum node count not met - need more nodes registered",
|
|
330
|
+
"MinSubnetDelegateStake": "Minimum delegate stake not met - need more stake delegated to subnet",
|
|
331
|
+
"Council": "Removed by governance council",
|
|
332
|
+
"EnactmentPeriod": "Enactment period expired without meeting activation requirements",
|
|
333
|
+
"MaxSubnets": "Maximum subnet limit reached on the network",
|
|
334
|
+
"Owner": "Deactivated by subnet owner",
|
|
335
|
+
"PauseExpired": "Pause period expired",
|
|
336
|
+
}
|
|
337
|
+
return REASONS.get(reason, f"Removed: {reason}")
|
|
338
|
+
|
|
339
|
+
def remove_subnet(
|
|
340
|
+
self, request: SubnetRemoveRequest, keypair: Keypair
|
|
341
|
+
) -> dict[str, Any]:
|
|
342
|
+
"""
|
|
343
|
+
Remove a subnet.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
request: Subnet removal request
|
|
347
|
+
keypair: Keypair for signing the transaction
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
dictionary with operation result
|
|
351
|
+
"""
|
|
352
|
+
try:
|
|
353
|
+
logger.info(f"Removing subnet: {request.subnet_id}")
|
|
354
|
+
|
|
355
|
+
call = self.substrate.compose_call(
|
|
356
|
+
call_module="Network",
|
|
357
|
+
call_function="owner_deactivate_subnet",
|
|
358
|
+
call_params={"subnet_id": request.subnet_id},
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
response = self._submit_extrinsic(call, keypair)
|
|
362
|
+
|
|
363
|
+
if response["success"]:
|
|
364
|
+
response["message"] = f"Subnet {request.subnet_id} removed successfully"
|
|
365
|
+
|
|
366
|
+
return response
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
logger.error(f"Error removing subnet: {e}")
|
|
370
|
+
return {"success": False, "error": str(e)}
|
|
371
|
+
|
|
372
|
+
# ============================================================================
|
|
373
|
+
# SUBNET PAUSE/UNPAUSE
|
|
374
|
+
# ============================================================================
|
|
375
|
+
|
|
376
|
+
def owner_pause_subnet(
|
|
377
|
+
self, request: SubnetActivateRequest, keypair: Keypair
|
|
378
|
+
) -> dict[str, Any]:
|
|
379
|
+
"""
|
|
380
|
+
Pause a subnet (owner only).
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
request: Subnet pause request
|
|
384
|
+
keypair: Keypair for signing the transaction
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
dictionary with operation result
|
|
388
|
+
"""
|
|
389
|
+
try:
|
|
390
|
+
logger.info(f"Pausing subnet: {request.subnet_id}")
|
|
391
|
+
|
|
392
|
+
call = self.substrate.compose_call(
|
|
393
|
+
call_module="Network",
|
|
394
|
+
call_function="owner_pause_subnet",
|
|
395
|
+
call_params={"subnet_id": request.subnet_id},
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
response = self._submit_extrinsic(call, keypair)
|
|
399
|
+
|
|
400
|
+
if response["success"]:
|
|
401
|
+
response["message"] = f"Subnet {request.subnet_id} paused successfully"
|
|
402
|
+
|
|
403
|
+
return response
|
|
404
|
+
|
|
405
|
+
except Exception as e:
|
|
406
|
+
logger.error(f"Error pausing subnet: {e}")
|
|
407
|
+
return {"success": False, "error": str(e)}
|
|
408
|
+
|
|
409
|
+
def owner_unpause_subnet(
|
|
410
|
+
self, request: SubnetActivateRequest, keypair: Keypair
|
|
411
|
+
) -> dict[str, Any]:
|
|
412
|
+
"""
|
|
413
|
+
Unpause a subnet (owner only).
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
request: Subnet unpause request
|
|
417
|
+
keypair: Keypair for signing the transaction
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
dictionary with operation result
|
|
421
|
+
"""
|
|
422
|
+
try:
|
|
423
|
+
logger.info(f"Unpausing subnet: {request.subnet_id}")
|
|
424
|
+
|
|
425
|
+
call = self.substrate.compose_call(
|
|
426
|
+
call_module="Network",
|
|
427
|
+
call_function="owner_unpause_subnet",
|
|
428
|
+
call_params={"subnet_id": request.subnet_id},
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
response = self._submit_extrinsic(call, keypair)
|
|
432
|
+
|
|
433
|
+
if response["success"]:
|
|
434
|
+
response["message"] = (
|
|
435
|
+
f"Subnet {request.subnet_id} unpaused successfully"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
return response
|
|
439
|
+
|
|
440
|
+
except Exception as e:
|
|
441
|
+
logger.error(f"Error unpausing subnet: {e}")
|
|
442
|
+
return {"success": False, "error": str(e)}
|
|
443
|
+
|
|
444
|
+
def owner_deactivate_subnet(
|
|
445
|
+
self, request: SubnetActivateRequest, keypair: Keypair
|
|
446
|
+
) -> dict[str, Any]:
|
|
447
|
+
"""
|
|
448
|
+
Deactivate a subnet (owner only).
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
request: Subnet deactivation request
|
|
452
|
+
keypair: Keypair for signing the transaction
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
dictionary with operation result
|
|
456
|
+
"""
|
|
457
|
+
try:
|
|
458
|
+
logger.info(f"Deactivating subnet: {request.subnet_id}")
|
|
459
|
+
|
|
460
|
+
call = self.substrate.compose_call(
|
|
461
|
+
call_module="Network",
|
|
462
|
+
call_function="owner_deactivate_subnet",
|
|
463
|
+
call_params={"subnet_id": request.subnet_id},
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
response = self._submit_extrinsic(call, keypair)
|
|
467
|
+
|
|
468
|
+
if response["success"]:
|
|
469
|
+
response["message"] = (
|
|
470
|
+
f"Subnet {request.subnet_id} deactivated successfully"
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
return response
|
|
474
|
+
|
|
475
|
+
except Exception as e:
|
|
476
|
+
logger.error(f"Error deactivating subnet: {e}")
|
|
477
|
+
return {"success": False, "error": str(e)}
|
|
478
|
+
|
|
479
|
+
# ============================================================================
|
|
480
|
+
# SUBNET METADATA UPDATES
|
|
481
|
+
# ============================================================================
|
|
482
|
+
|
|
483
|
+
def owner_update_name(
|
|
484
|
+
self, request: SubnetUpdateRequest, keypair: Keypair
|
|
485
|
+
) -> dict[str, Any]:
|
|
486
|
+
"""
|
|
487
|
+
Update subnet name (owner only).
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
request: Subnet name update request
|
|
491
|
+
keypair: Keypair for signing the transaction
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
dictionary with operation result
|
|
495
|
+
"""
|
|
496
|
+
try:
|
|
497
|
+
logger.info(f"Updating subnet {request.subnet_id} name")
|
|
498
|
+
|
|
499
|
+
call = self.substrate.compose_call(
|
|
500
|
+
call_module="Network",
|
|
501
|
+
call_function="owner_update_name",
|
|
502
|
+
call_params={
|
|
503
|
+
"subnet_id": request.subnet_id,
|
|
504
|
+
"value": request.value.encode("utf-8"),
|
|
505
|
+
},
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
response = self._submit_extrinsic(call, keypair)
|
|
509
|
+
|
|
510
|
+
if response["success"]:
|
|
511
|
+
response["message"] = (
|
|
512
|
+
f"Subnet {request.subnet_id} name updated successfully"
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
return response
|
|
516
|
+
|
|
517
|
+
except Exception as e:
|
|
518
|
+
logger.error(f"Error updating subnet name: {e}")
|
|
519
|
+
return {"success": False, "error": str(e)}
|
|
520
|
+
|
|
521
|
+
def owner_update_repo(
|
|
522
|
+
self, request: SubnetUpdateRequest, keypair: Keypair
|
|
523
|
+
) -> dict[str, Any]:
|
|
524
|
+
"""
|
|
525
|
+
Update subnet repository URL (owner only).
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
request: Subnet repo update request
|
|
529
|
+
keypair: Keypair for signing the transaction
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
dictionary with operation result
|
|
533
|
+
"""
|
|
534
|
+
try:
|
|
535
|
+
logger.info(f"Updating subnet {request.subnet_id} repository")
|
|
536
|
+
|
|
537
|
+
call = self.substrate.compose_call(
|
|
538
|
+
call_module="Network",
|
|
539
|
+
call_function="owner_update_repo",
|
|
540
|
+
call_params={
|
|
541
|
+
"subnet_id": request.subnet_id,
|
|
542
|
+
"value": request.value.encode("utf-8"),
|
|
543
|
+
},
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
response = self._submit_extrinsic(call, keypair)
|
|
547
|
+
|
|
548
|
+
if response["success"]:
|
|
549
|
+
response["message"] = (
|
|
550
|
+
f"Subnet {request.subnet_id} repository updated successfully"
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
return response
|
|
554
|
+
|
|
555
|
+
except Exception as e:
|
|
556
|
+
logger.error(f"Error updating subnet repository: {e}")
|
|
557
|
+
return {"success": False, "error": str(e)}
|
|
558
|
+
|
|
559
|
+
def owner_update_description(
|
|
560
|
+
self, request: SubnetUpdateRequest, keypair: Keypair
|
|
561
|
+
) -> dict[str, Any]:
|
|
562
|
+
"""
|
|
563
|
+
Update subnet description (owner only).
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
request: Subnet description update request
|
|
567
|
+
keypair: Keypair for signing the transaction
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
dictionary with operation result
|
|
571
|
+
"""
|
|
572
|
+
try:
|
|
573
|
+
logger.info(f"Updating subnet {request.subnet_id} description")
|
|
574
|
+
|
|
575
|
+
call = self.substrate.compose_call(
|
|
576
|
+
call_module="Network",
|
|
577
|
+
call_function="owner_update_description",
|
|
578
|
+
call_params={
|
|
579
|
+
"subnet_id": request.subnet_id,
|
|
580
|
+
"value": request.value.encode("utf-8"),
|
|
581
|
+
},
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
response = self._submit_extrinsic(call, keypair)
|
|
585
|
+
|
|
586
|
+
if response["success"]:
|
|
587
|
+
response["message"] = (
|
|
588
|
+
f"Subnet {request.subnet_id} description updated successfully"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
return response
|
|
592
|
+
|
|
593
|
+
except Exception as e:
|
|
594
|
+
logger.error(f"Error updating subnet description: {e}")
|
|
595
|
+
return {"success": False, "error": str(e)}
|
|
596
|
+
|
|
597
|
+
def owner_update_misc(
|
|
598
|
+
self, request: SubnetUpdateRequest, keypair: Keypair
|
|
599
|
+
) -> dict[str, Any]:
|
|
600
|
+
"""
|
|
601
|
+
Update subnet miscellaneous info (owner only).
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
request: Subnet misc update request
|
|
605
|
+
keypair: Keypair for signing the transaction
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
dictionary with operation result
|
|
609
|
+
"""
|
|
610
|
+
try:
|
|
611
|
+
logger.info(f"Updating subnet {request.subnet_id} misc info")
|
|
612
|
+
|
|
613
|
+
call = self.substrate.compose_call(
|
|
614
|
+
call_module="Network",
|
|
615
|
+
call_function="owner_update_misc",
|
|
616
|
+
call_params={
|
|
617
|
+
"subnet_id": request.subnet_id,
|
|
618
|
+
"value": request.value.encode("utf-8"),
|
|
619
|
+
},
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
response = self._submit_extrinsic(call, keypair)
|
|
623
|
+
|
|
624
|
+
if response["success"]:
|
|
625
|
+
response["message"] = (
|
|
626
|
+
f"Subnet {request.subnet_id} misc info updated successfully"
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
return response
|
|
630
|
+
|
|
631
|
+
except Exception as e:
|
|
632
|
+
logger.error(f"Error updating subnet misc info: {e}")
|
|
633
|
+
return {"success": False, "error": str(e)}
|
|
634
|
+
|
|
635
|
+
# ============================================================================
|
|
636
|
+
# SUBNET CONFIGURATION UPDATES
|
|
637
|
+
# ============================================================================
|
|
638
|
+
|
|
639
|
+
def owner_update_parameters(
|
|
640
|
+
self,
|
|
641
|
+
subnet_id: int,
|
|
642
|
+
*,
|
|
643
|
+
new_name: Optional[str] = None,
|
|
644
|
+
new_repo: Optional[str] = None,
|
|
645
|
+
target_node_registrations: Optional[int] = None,
|
|
646
|
+
node_burn_rate_alpha: Optional[int] = None,
|
|
647
|
+
queue_immunity_epochs: Optional[int] = None,
|
|
648
|
+
min_weight_decrease_threshold: Optional[int] = None,
|
|
649
|
+
min_node_reputation: Optional[int] = None,
|
|
650
|
+
absent_reputation_penalty: Optional[int] = None,
|
|
651
|
+
included_reputation_boost: Optional[int] = None,
|
|
652
|
+
below_min_weight_penalty: Optional[int] = None,
|
|
653
|
+
non_attestor_penalty: Optional[int] = None,
|
|
654
|
+
validator_absent_penalty: Optional[int] = None,
|
|
655
|
+
validator_non_consensus_penalty: Optional[int] = None,
|
|
656
|
+
non_consensus_attestor_penalty: Optional[int] = None,
|
|
657
|
+
keypair: Keypair,
|
|
658
|
+
) -> list[tuple[str, dict[str, Any]]]:
|
|
659
|
+
"""
|
|
660
|
+
Perform one or more owner subnet parameter updates in sequence.
|
|
661
|
+
|
|
662
|
+
Returns a list of (field_name, result_dict) tuples.
|
|
663
|
+
"""
|
|
664
|
+
results: list[tuple[str, dict[str, Any]]] = []
|
|
665
|
+
|
|
666
|
+
# Metadata updates
|
|
667
|
+
if new_name is not None:
|
|
668
|
+
try:
|
|
669
|
+
request = SubnetUpdateRequest(subnet_id=subnet_id, value=new_name)
|
|
670
|
+
result = self.owner_update_name(request, keypair)
|
|
671
|
+
except Exception as e:
|
|
672
|
+
logger.error(f"Error updating subnet name: {e}")
|
|
673
|
+
result = {"success": False, "error": str(e)}
|
|
674
|
+
results.append(("name", result))
|
|
675
|
+
|
|
676
|
+
if new_repo is not None:
|
|
677
|
+
try:
|
|
678
|
+
request = SubnetUpdateRequest(subnet_id=subnet_id, value=new_repo)
|
|
679
|
+
result = self.owner_update_repo(request, keypair)
|
|
680
|
+
except Exception as e:
|
|
681
|
+
logger.error(f"Error updating subnet repo: {e}")
|
|
682
|
+
result = {"success": False, "error": str(e)}
|
|
683
|
+
results.append(("repository", result))
|
|
684
|
+
|
|
685
|
+
# Config updates
|
|
686
|
+
if target_node_registrations is not None:
|
|
687
|
+
try:
|
|
688
|
+
request = SubnetConfigUpdateRequest(
|
|
689
|
+
subnet_id=subnet_id, value=target_node_registrations
|
|
690
|
+
)
|
|
691
|
+
result = self.owner_update_target_node_registrations_per_epoch(
|
|
692
|
+
request, keypair
|
|
693
|
+
)
|
|
694
|
+
except Exception as e:
|
|
695
|
+
logger.error(f"Error updating target node registrations per epoch: {e}")
|
|
696
|
+
result = {"success": False, "error": str(e)}
|
|
697
|
+
results.append(("target_node_registrations", result))
|
|
698
|
+
|
|
699
|
+
if node_burn_rate_alpha is not None:
|
|
700
|
+
try:
|
|
701
|
+
request = SubnetConfigUpdateRequest(
|
|
702
|
+
subnet_id=subnet_id, value=node_burn_rate_alpha
|
|
703
|
+
)
|
|
704
|
+
result = self.owner_update_node_burn_rate_alpha(request, keypair)
|
|
705
|
+
except Exception as e:
|
|
706
|
+
logger.error(f"Error updating node burn rate alpha: {e}")
|
|
707
|
+
result = {"success": False, "error": str(e)}
|
|
708
|
+
results.append(("node_burn_rate_alpha", result))
|
|
709
|
+
|
|
710
|
+
if queue_immunity_epochs is not None:
|
|
711
|
+
try:
|
|
712
|
+
request = SubnetConfigUpdateRequest(
|
|
713
|
+
subnet_id=subnet_id, value=queue_immunity_epochs
|
|
714
|
+
)
|
|
715
|
+
result = self.owner_update_queue_immunity_epochs(request, keypair)
|
|
716
|
+
except Exception as e:
|
|
717
|
+
logger.error(f"Error updating queue immunity epochs: {e}")
|
|
718
|
+
result = {"success": False, "error": str(e)}
|
|
719
|
+
results.append(("queue_immunity_epochs", result))
|
|
720
|
+
|
|
721
|
+
if min_weight_decrease_threshold is not None:
|
|
722
|
+
try:
|
|
723
|
+
request = SubnetConfigUpdateRequest(
|
|
724
|
+
subnet_id=subnet_id, value=min_weight_decrease_threshold
|
|
725
|
+
)
|
|
726
|
+
result = self.owner_update_subnet_node_min_weight_decrease_reputation_threshold(
|
|
727
|
+
request, keypair
|
|
728
|
+
)
|
|
729
|
+
except Exception as e:
|
|
730
|
+
logger.error(
|
|
731
|
+
f"Error updating min weight decrease reputation threshold: {e}"
|
|
732
|
+
)
|
|
733
|
+
result = {"success": False, "error": str(e)}
|
|
734
|
+
results.append(("min_weight_decrease_threshold", result))
|
|
735
|
+
|
|
736
|
+
if min_node_reputation is not None:
|
|
737
|
+
try:
|
|
738
|
+
request = SubnetConfigUpdateRequest(
|
|
739
|
+
subnet_id=subnet_id, value=min_node_reputation
|
|
740
|
+
)
|
|
741
|
+
result = self.owner_update_min_subnet_node_reputation(request, keypair)
|
|
742
|
+
except Exception as e:
|
|
743
|
+
logger.error(f"Error updating min node reputation: {e}")
|
|
744
|
+
result = {"success": False, "error": str(e)}
|
|
745
|
+
results.append(("min_node_reputation", result))
|
|
746
|
+
|
|
747
|
+
if absent_reputation_penalty is not None:
|
|
748
|
+
try:
|
|
749
|
+
request = SubnetConfigUpdateRequest(
|
|
750
|
+
subnet_id=subnet_id, value=absent_reputation_penalty
|
|
751
|
+
)
|
|
752
|
+
result = self.owner_update_absent_decrease_reputation_factor(
|
|
753
|
+
request, keypair
|
|
754
|
+
)
|
|
755
|
+
except Exception as e:
|
|
756
|
+
logger.error(f"Error updating absent reputation penalty: {e}")
|
|
757
|
+
result = {"success": False, "error": str(e)}
|
|
758
|
+
results.append(("absent_reputation_penalty", result))
|
|
759
|
+
|
|
760
|
+
if included_reputation_boost is not None:
|
|
761
|
+
try:
|
|
762
|
+
request = SubnetConfigUpdateRequest(
|
|
763
|
+
subnet_id=subnet_id, value=included_reputation_boost
|
|
764
|
+
)
|
|
765
|
+
result = self.owner_update_included_increase_reputation_factor(
|
|
766
|
+
request, keypair
|
|
767
|
+
)
|
|
768
|
+
except Exception as e:
|
|
769
|
+
logger.error(f"Error updating included reputation boost: {e}")
|
|
770
|
+
result = {"success": False, "error": str(e)}
|
|
771
|
+
results.append(("included_reputation_boost", result))
|
|
772
|
+
|
|
773
|
+
if below_min_weight_penalty is not None:
|
|
774
|
+
try:
|
|
775
|
+
request = SubnetConfigUpdateRequest(
|
|
776
|
+
subnet_id=subnet_id, value=below_min_weight_penalty
|
|
777
|
+
)
|
|
778
|
+
result = self.owner_update_below_min_weight_decrease_reputation_factor(
|
|
779
|
+
request, keypair
|
|
780
|
+
)
|
|
781
|
+
except Exception as e:
|
|
782
|
+
logger.error(f"Error updating below-min weight penalty: {e}")
|
|
783
|
+
result = {"success": False, "error": str(e)}
|
|
784
|
+
results.append(("below_min_weight_penalty", result))
|
|
785
|
+
|
|
786
|
+
if non_attestor_penalty is not None:
|
|
787
|
+
try:
|
|
788
|
+
request = SubnetConfigUpdateRequest(
|
|
789
|
+
subnet_id=subnet_id, value=non_attestor_penalty
|
|
790
|
+
)
|
|
791
|
+
result = self.owner_update_non_attestor_decrease_reputation_factor(
|
|
792
|
+
request, keypair
|
|
793
|
+
)
|
|
794
|
+
except Exception as e:
|
|
795
|
+
logger.error(f"Error updating non-attestor penalty: {e}")
|
|
796
|
+
result = {"success": False, "error": str(e)}
|
|
797
|
+
results.append(("non_attestor_penalty", result))
|
|
798
|
+
|
|
799
|
+
if validator_absent_penalty is not None:
|
|
800
|
+
try:
|
|
801
|
+
request = SubnetConfigUpdateRequest(
|
|
802
|
+
subnet_id=subnet_id, value=validator_absent_penalty
|
|
803
|
+
)
|
|
804
|
+
result = self.owner_update_validator_absent_decrease_reputation_factor(
|
|
805
|
+
request, keypair
|
|
806
|
+
)
|
|
807
|
+
except Exception as e:
|
|
808
|
+
logger.error(f"Error updating validator absent penalty: {e}")
|
|
809
|
+
result = {"success": False, "error": str(e)}
|
|
810
|
+
results.append(("validator_absent_penalty", result))
|
|
811
|
+
|
|
812
|
+
if validator_non_consensus_penalty is not None:
|
|
813
|
+
try:
|
|
814
|
+
request = SubnetConfigUpdateRequest(
|
|
815
|
+
subnet_id=subnet_id, value=validator_non_consensus_penalty
|
|
816
|
+
)
|
|
817
|
+
result = self.owner_update_validator_non_consensus_decrease_reputation_factor(
|
|
818
|
+
request, keypair
|
|
819
|
+
)
|
|
820
|
+
except Exception as e:
|
|
821
|
+
logger.error(f"Error updating validator non-consensus penalty: {e}")
|
|
822
|
+
result = {"success": False, "error": str(e)}
|
|
823
|
+
results.append(("validator_non_consensus_penalty", result))
|
|
824
|
+
|
|
825
|
+
if non_consensus_attestor_penalty is not None:
|
|
826
|
+
try:
|
|
827
|
+
request = SubnetConfigUpdateRequest(
|
|
828
|
+
subnet_id=subnet_id, value=non_consensus_attestor_penalty
|
|
829
|
+
)
|
|
830
|
+
result = (
|
|
831
|
+
self.owner_update_non_consensus_attestor_decrease_reputation_factor(
|
|
832
|
+
request, keypair
|
|
833
|
+
)
|
|
834
|
+
)
|
|
835
|
+
except Exception as e:
|
|
836
|
+
logger.error(f"Error updating non-consensus attestor penalty: {e}")
|
|
837
|
+
result = {"success": False, "error": str(e)}
|
|
838
|
+
results.append(("non_consensus_attestor_penalty", result))
|
|
839
|
+
|
|
840
|
+
return results
|
|
841
|
+
|
|
842
|
+
def owner_update_churn_limit(
|
|
843
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
844
|
+
) -> dict[str, Any]:
|
|
845
|
+
"""
|
|
846
|
+
Update subnet churn limit (owner only).
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
request: Subnet churn limit update request
|
|
850
|
+
keypair: Keypair for signing the transaction
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
dictionary with operation result
|
|
854
|
+
"""
|
|
855
|
+
try:
|
|
856
|
+
logger.info(f"Updating subnet {request.subnet_id} churn limit")
|
|
857
|
+
|
|
858
|
+
call = self.substrate.compose_call(
|
|
859
|
+
call_module="Network",
|
|
860
|
+
call_function="owner_update_churn_limit",
|
|
861
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
response = self._submit_extrinsic(call, keypair)
|
|
865
|
+
|
|
866
|
+
if response["success"]:
|
|
867
|
+
response["message"] = (
|
|
868
|
+
f"Subnet {request.subnet_id} churn limit updated successfully"
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
return response
|
|
872
|
+
|
|
873
|
+
except Exception as e:
|
|
874
|
+
logger.error(f"Error updating subnet churn limit: {e}")
|
|
875
|
+
return {"success": False, "error": str(e)}
|
|
876
|
+
|
|
877
|
+
def owner_update_registration_queue_epochs(
|
|
878
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
879
|
+
) -> dict[str, Any]:
|
|
880
|
+
"""
|
|
881
|
+
Update subnet registration queue epochs (owner only).
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
request: Subnet queue epochs update request
|
|
885
|
+
keypair: Keypair for signing the transaction
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
dictionary with operation result
|
|
889
|
+
"""
|
|
890
|
+
try:
|
|
891
|
+
logger.info(
|
|
892
|
+
f"Updating subnet {request.subnet_id} registration queue epochs"
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
call = self.substrate.compose_call(
|
|
896
|
+
call_module="Network",
|
|
897
|
+
call_function="owner_update_registration_queue_epochs",
|
|
898
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
response = self._submit_extrinsic(call, keypair)
|
|
902
|
+
|
|
903
|
+
if response["success"]:
|
|
904
|
+
response["message"] = (
|
|
905
|
+
f"Subnet {request.subnet_id} registration queue epochs updated successfully"
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
return response
|
|
909
|
+
|
|
910
|
+
except Exception as e:
|
|
911
|
+
logger.error(f"Error updating subnet registration queue epochs: {e}")
|
|
912
|
+
return {"success": False, "error": str(e)}
|
|
913
|
+
|
|
914
|
+
def owner_update_target_node_registrations_per_epoch(
|
|
915
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
916
|
+
) -> dict[str, Any]:
|
|
917
|
+
"""
|
|
918
|
+
Update target node registrations per epoch (owner only).
|
|
919
|
+
|
|
920
|
+
Args:
|
|
921
|
+
request: Target node registrations update request
|
|
922
|
+
keypair: Keypair for signing the transaction
|
|
923
|
+
|
|
924
|
+
Returns:
|
|
925
|
+
dictionary with operation result
|
|
926
|
+
"""
|
|
927
|
+
return self._execute_owner_value_update(
|
|
928
|
+
request,
|
|
929
|
+
keypair,
|
|
930
|
+
call_function="owner_update_target_node_registrations_per_epoch",
|
|
931
|
+
success_message=(
|
|
932
|
+
"Subnet {subnet_id} target node registrations per epoch updated successfully"
|
|
933
|
+
),
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
def owner_update_activation_grace_epochs(
|
|
937
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
938
|
+
) -> dict[str, Any]:
|
|
939
|
+
"""
|
|
940
|
+
Update subnet activation grace epochs (owner only).
|
|
941
|
+
|
|
942
|
+
Args:
|
|
943
|
+
request: Subnet grace epochs update request
|
|
944
|
+
keypair: Keypair for signing the transaction
|
|
945
|
+
|
|
946
|
+
Returns:
|
|
947
|
+
dictionary with operation result
|
|
948
|
+
"""
|
|
949
|
+
try:
|
|
950
|
+
logger.info(f"Updating subnet {request.subnet_id} activation grace epochs")
|
|
951
|
+
|
|
952
|
+
call = self.substrate.compose_call(
|
|
953
|
+
call_module="Network",
|
|
954
|
+
call_function="owner_update_activation_grace_epochs",
|
|
955
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
response = self._submit_extrinsic(call, keypair)
|
|
959
|
+
|
|
960
|
+
if response["success"]:
|
|
961
|
+
response["message"] = (
|
|
962
|
+
f"Subnet {request.subnet_id} activation grace epochs updated successfully"
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
return response
|
|
966
|
+
|
|
967
|
+
except Exception as e:
|
|
968
|
+
logger.error(f"Error updating subnet activation grace epochs: {e}")
|
|
969
|
+
return {"success": False, "error": str(e)}
|
|
970
|
+
|
|
971
|
+
def owner_update_idle_classification_epochs(
|
|
972
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
973
|
+
) -> dict[str, Any]:
|
|
974
|
+
"""
|
|
975
|
+
Update subnet idle classification epochs (owner only).
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
request: Subnet idle epochs update request
|
|
979
|
+
keypair: Keypair for signing the transaction
|
|
980
|
+
|
|
981
|
+
Returns:
|
|
982
|
+
dictionary with operation result
|
|
983
|
+
"""
|
|
984
|
+
try:
|
|
985
|
+
logger.info(
|
|
986
|
+
f"Updating subnet {request.subnet_id} idle classification epochs"
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
call = self.substrate.compose_call(
|
|
990
|
+
call_module="Network",
|
|
991
|
+
call_function="owner_update_idle_classification_epochs",
|
|
992
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
response = self._submit_extrinsic(call, keypair)
|
|
996
|
+
|
|
997
|
+
if response["success"]:
|
|
998
|
+
response["message"] = (
|
|
999
|
+
f"Subnet {request.subnet_id} idle classification epochs updated successfully"
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
return response
|
|
1003
|
+
|
|
1004
|
+
except Exception as e:
|
|
1005
|
+
logger.error(f"Error updating subnet idle classification epochs: {e}")
|
|
1006
|
+
return {"success": False, "error": str(e)}
|
|
1007
|
+
|
|
1008
|
+
def owner_update_included_classification_epochs(
|
|
1009
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1010
|
+
) -> dict[str, Any]:
|
|
1011
|
+
"""
|
|
1012
|
+
Update subnet included classification epochs (owner only).
|
|
1013
|
+
|
|
1014
|
+
Args:
|
|
1015
|
+
request: Subnet included epochs update request
|
|
1016
|
+
keypair: Keypair for signing the transaction
|
|
1017
|
+
|
|
1018
|
+
Returns:
|
|
1019
|
+
dictionary with operation result
|
|
1020
|
+
"""
|
|
1021
|
+
try:
|
|
1022
|
+
logger.info(
|
|
1023
|
+
f"Updating subnet {request.subnet_id} included classification epochs"
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
call = self.substrate.compose_call(
|
|
1027
|
+
call_module="Network",
|
|
1028
|
+
call_function="owner_update_included_classification_epochs",
|
|
1029
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1033
|
+
|
|
1034
|
+
if response["success"]:
|
|
1035
|
+
response["message"] = (
|
|
1036
|
+
f"Subnet {request.subnet_id} included classification epochs updated successfully"
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
return response
|
|
1040
|
+
|
|
1041
|
+
except Exception as e:
|
|
1042
|
+
logger.error(f"Error updating subnet included classification epochs: {e}")
|
|
1043
|
+
return {"success": False, "error": str(e)}
|
|
1044
|
+
|
|
1045
|
+
def owner_update_queue_immunity_epochs(
|
|
1046
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1047
|
+
) -> dict[str, Any]:
|
|
1048
|
+
"""
|
|
1049
|
+
Update queue immunity epochs (owner only).
|
|
1050
|
+
|
|
1051
|
+
Args:
|
|
1052
|
+
request: Queue immunity epochs update request
|
|
1053
|
+
keypair: Keypair for signing the transaction
|
|
1054
|
+
|
|
1055
|
+
Returns:
|
|
1056
|
+
dictionary with operation result
|
|
1057
|
+
"""
|
|
1058
|
+
return self._execute_owner_value_update(
|
|
1059
|
+
request,
|
|
1060
|
+
keypair,
|
|
1061
|
+
call_function="owner_update_queue_immunity_epochs",
|
|
1062
|
+
success_message=(
|
|
1063
|
+
"Subnet {subnet_id} queue immunity epochs updated successfully"
|
|
1064
|
+
),
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
def owner_update_max_node_penalties(
|
|
1068
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1069
|
+
) -> dict[str, Any]:
|
|
1070
|
+
"""
|
|
1071
|
+
Update subnet max node penalties (owner only).
|
|
1072
|
+
|
|
1073
|
+
Args:
|
|
1074
|
+
request: Subnet max penalties update request
|
|
1075
|
+
keypair: Keypair for signing the transaction
|
|
1076
|
+
|
|
1077
|
+
Returns:
|
|
1078
|
+
dictionary with operation result
|
|
1079
|
+
"""
|
|
1080
|
+
try:
|
|
1081
|
+
logger.info(f"Updating subnet {request.subnet_id} max node penalties")
|
|
1082
|
+
|
|
1083
|
+
call = self.substrate.compose_call(
|
|
1084
|
+
call_module="Network",
|
|
1085
|
+
call_function="owner_update_max_node_penalties",
|
|
1086
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1090
|
+
|
|
1091
|
+
if response["success"]:
|
|
1092
|
+
response["message"] = (
|
|
1093
|
+
f"Subnet {request.subnet_id} max node penalties updated successfully"
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
return response
|
|
1097
|
+
|
|
1098
|
+
except Exception as e:
|
|
1099
|
+
logger.error(f"Error updating subnet max node penalties: {e}")
|
|
1100
|
+
return {"success": False, "error": str(e)}
|
|
1101
|
+
|
|
1102
|
+
# ============================================================================
|
|
1103
|
+
# SUBNET ACCESS CONTROL UPDATES
|
|
1104
|
+
# ============================================================================
|
|
1105
|
+
|
|
1106
|
+
def owner_add_initial_coldkeys(
|
|
1107
|
+
self, subnet_id: int, coldkeys: list[str], keypair: Keypair
|
|
1108
|
+
) -> dict[str, Any]:
|
|
1109
|
+
"""
|
|
1110
|
+
Add initial coldkeys to subnet (owner only).
|
|
1111
|
+
|
|
1112
|
+
Args:
|
|
1113
|
+
subnet_id: The subnet ID
|
|
1114
|
+
coldkeys: list of coldkey account IDs to add
|
|
1115
|
+
keypair: Keypair for signing the transaction
|
|
1116
|
+
|
|
1117
|
+
Returns:
|
|
1118
|
+
dictionary with operation result
|
|
1119
|
+
"""
|
|
1120
|
+
try:
|
|
1121
|
+
logger.info(f"Adding initial coldkeys to subnet {subnet_id}")
|
|
1122
|
+
|
|
1123
|
+
call = self.substrate.compose_call(
|
|
1124
|
+
call_module="Network",
|
|
1125
|
+
call_function="owner_add_initial_coldkeys",
|
|
1126
|
+
call_params={"subnet_id": subnet_id, "coldkeys": coldkeys},
|
|
1127
|
+
)
|
|
1128
|
+
|
|
1129
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1130
|
+
|
|
1131
|
+
if response["success"]:
|
|
1132
|
+
response["message"] = (
|
|
1133
|
+
f"Subnet {subnet_id} initial coldkeys added successfully"
|
|
1134
|
+
)
|
|
1135
|
+
|
|
1136
|
+
return response
|
|
1137
|
+
|
|
1138
|
+
except Exception as e:
|
|
1139
|
+
logger.error(f"Error adding initial coldkeys: {e}")
|
|
1140
|
+
return {"success": False, "error": str(e)}
|
|
1141
|
+
|
|
1142
|
+
def owner_remove_initial_coldkeys(
|
|
1143
|
+
self, subnet_id: int, coldkeys: list[str], keypair: Keypair
|
|
1144
|
+
) -> dict[str, Any]:
|
|
1145
|
+
"""
|
|
1146
|
+
Remove initial coldkeys from subnet (owner only).
|
|
1147
|
+
|
|
1148
|
+
Args:
|
|
1149
|
+
subnet_id: The subnet ID
|
|
1150
|
+
coldkeys: list of coldkey account IDs to remove
|
|
1151
|
+
keypair: Keypair for signing the transaction
|
|
1152
|
+
|
|
1153
|
+
Returns:
|
|
1154
|
+
dictionary with operation result
|
|
1155
|
+
"""
|
|
1156
|
+
try:
|
|
1157
|
+
logger.info(f"Removing initial coldkeys from subnet {subnet_id}")
|
|
1158
|
+
|
|
1159
|
+
call = self.substrate.compose_call(
|
|
1160
|
+
call_module="Network",
|
|
1161
|
+
call_function="owner_remove_initial_coldkeys",
|
|
1162
|
+
call_params={"subnet_id": subnet_id, "coldkeys": coldkeys},
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1166
|
+
|
|
1167
|
+
if response["success"]:
|
|
1168
|
+
response["message"] = (
|
|
1169
|
+
f"Subnet {subnet_id} initial coldkeys removed successfully"
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
return response
|
|
1173
|
+
|
|
1174
|
+
except Exception as e:
|
|
1175
|
+
logger.error(f"Error removing initial coldkeys: {e}")
|
|
1176
|
+
return {"success": False, "error": str(e)}
|
|
1177
|
+
|
|
1178
|
+
def owner_update_key_types(
|
|
1179
|
+
self, subnet_id: int, key_types: list[KeyType], keypair: Keypair
|
|
1180
|
+
) -> dict[str, Any]:
|
|
1181
|
+
"""
|
|
1182
|
+
Update subnet key types (owner only).
|
|
1183
|
+
|
|
1184
|
+
Args:
|
|
1185
|
+
subnet_id: The subnet ID
|
|
1186
|
+
key_types: list of key types to update
|
|
1187
|
+
keypair: Keypair for signing the transaction
|
|
1188
|
+
|
|
1189
|
+
Returns:
|
|
1190
|
+
dictionary with operation result
|
|
1191
|
+
"""
|
|
1192
|
+
try:
|
|
1193
|
+
logger.info(f"Updating key types for subnet {subnet_id}")
|
|
1194
|
+
|
|
1195
|
+
call = self.substrate.compose_call(
|
|
1196
|
+
call_module="Network",
|
|
1197
|
+
call_function="owner_update_key_types",
|
|
1198
|
+
call_params={
|
|
1199
|
+
"subnet_id": subnet_id,
|
|
1200
|
+
"key_types": [kt.value for kt in key_types],
|
|
1201
|
+
},
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1205
|
+
|
|
1206
|
+
if response["success"]:
|
|
1207
|
+
response["message"] = (
|
|
1208
|
+
f"Subnet {subnet_id} key types updated successfully"
|
|
1209
|
+
)
|
|
1210
|
+
|
|
1211
|
+
return response
|
|
1212
|
+
|
|
1213
|
+
except Exception as e:
|
|
1214
|
+
logger.error(f"Error updating key types: {e}")
|
|
1215
|
+
return {"success": False, "error": str(e)}
|
|
1216
|
+
|
|
1217
|
+
# ============================================================================
|
|
1218
|
+
# SUBNET NODE MANAGEMENT
|
|
1219
|
+
# ============================================================================
|
|
1220
|
+
|
|
1221
|
+
def owner_remove_subnet_node(
|
|
1222
|
+
self, subnet_id: int, subnet_node_id: int, keypair: Keypair
|
|
1223
|
+
) -> dict[str, Any]:
|
|
1224
|
+
"""
|
|
1225
|
+
Remove a subnet node (owner only).
|
|
1226
|
+
|
|
1227
|
+
Args:
|
|
1228
|
+
subnet_id: The subnet ID
|
|
1229
|
+
subnet_node_id: The subnet node ID to remove
|
|
1230
|
+
keypair: Keypair for signing the transaction
|
|
1231
|
+
|
|
1232
|
+
Returns:
|
|
1233
|
+
dictionary with operation result
|
|
1234
|
+
"""
|
|
1235
|
+
try:
|
|
1236
|
+
logger.info(f"Removing subnet {subnet_id} node {subnet_node_id}")
|
|
1237
|
+
|
|
1238
|
+
call = self.substrate.compose_call(
|
|
1239
|
+
call_module="Network",
|
|
1240
|
+
call_function="owner_remove_subnet_node",
|
|
1241
|
+
call_params={"subnet_id": subnet_id, "subnet_node_id": subnet_node_id},
|
|
1242
|
+
)
|
|
1243
|
+
|
|
1244
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1245
|
+
|
|
1246
|
+
if response["success"]:
|
|
1247
|
+
response["message"] = (
|
|
1248
|
+
f"Subnet {subnet_id} node {subnet_node_id} removed successfully"
|
|
1249
|
+
)
|
|
1250
|
+
|
|
1251
|
+
return response
|
|
1252
|
+
|
|
1253
|
+
except Exception as e:
|
|
1254
|
+
logger.error(f"Error removing subnet node: {e}")
|
|
1255
|
+
return {"success": False, "error": str(e)}
|
|
1256
|
+
|
|
1257
|
+
# ============================================================================
|
|
1258
|
+
# SUBNET STAKE UPDATES
|
|
1259
|
+
# ============================================================================
|
|
1260
|
+
|
|
1261
|
+
def owner_update_min_stake(
|
|
1262
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1263
|
+
) -> dict[str, Any]:
|
|
1264
|
+
"""
|
|
1265
|
+
Update subnet minimum stake (owner only).
|
|
1266
|
+
|
|
1267
|
+
Args:
|
|
1268
|
+
request: Subnet min stake update request
|
|
1269
|
+
keypair: Keypair for signing the transaction
|
|
1270
|
+
|
|
1271
|
+
Returns:
|
|
1272
|
+
dictionary with operation result
|
|
1273
|
+
"""
|
|
1274
|
+
try:
|
|
1275
|
+
logger.info(f"Updating subnet {request.subnet_id} min stake")
|
|
1276
|
+
|
|
1277
|
+
call = self.substrate.compose_call(
|
|
1278
|
+
call_module="Network",
|
|
1279
|
+
call_function="owner_update_min_stake",
|
|
1280
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1284
|
+
|
|
1285
|
+
if response["success"]:
|
|
1286
|
+
response["message"] = (
|
|
1287
|
+
f"Subnet {request.subnet_id} min stake updated successfully"
|
|
1288
|
+
)
|
|
1289
|
+
|
|
1290
|
+
return response
|
|
1291
|
+
|
|
1292
|
+
except Exception as e:
|
|
1293
|
+
logger.error(f"Error updating subnet min stake: {e}")
|
|
1294
|
+
return {"success": False, "error": str(e)}
|
|
1295
|
+
|
|
1296
|
+
def owner_update_max_stake(
|
|
1297
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1298
|
+
) -> dict[str, Any]:
|
|
1299
|
+
"""
|
|
1300
|
+
Update subnet maximum stake (owner only).
|
|
1301
|
+
|
|
1302
|
+
Args:
|
|
1303
|
+
request: Subnet max stake update request
|
|
1304
|
+
keypair: Keypair for signing the transaction
|
|
1305
|
+
|
|
1306
|
+
Returns:
|
|
1307
|
+
dictionary with operation result
|
|
1308
|
+
"""
|
|
1309
|
+
try:
|
|
1310
|
+
logger.info(f"Updating subnet {request.subnet_id} max stake")
|
|
1311
|
+
|
|
1312
|
+
call = self.substrate.compose_call(
|
|
1313
|
+
call_module="Network",
|
|
1314
|
+
call_function="owner_update_max_stake",
|
|
1315
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1319
|
+
|
|
1320
|
+
if response["success"]:
|
|
1321
|
+
response["message"] = (
|
|
1322
|
+
f"Subnet {request.subnet_id} max stake updated successfully"
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
return response
|
|
1326
|
+
|
|
1327
|
+
except Exception as e:
|
|
1328
|
+
logger.error(f"Error updating subnet max stake: {e}")
|
|
1329
|
+
return {"success": False, "error": str(e)}
|
|
1330
|
+
|
|
1331
|
+
def owner_update_delegate_stake_percentage(
|
|
1332
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1333
|
+
) -> dict[str, Any]:
|
|
1334
|
+
"""
|
|
1335
|
+
Update subnet delegate stake percentage (owner only).
|
|
1336
|
+
|
|
1337
|
+
Args:
|
|
1338
|
+
request: Subnet delegate stake percentage update request
|
|
1339
|
+
keypair: Keypair for signing the transaction
|
|
1340
|
+
|
|
1341
|
+
Returns:
|
|
1342
|
+
dictionary with operation result
|
|
1343
|
+
"""
|
|
1344
|
+
try:
|
|
1345
|
+
logger.info(
|
|
1346
|
+
f"Updating subnet {request.subnet_id} delegate stake percentage"
|
|
1347
|
+
)
|
|
1348
|
+
|
|
1349
|
+
call = self.substrate.compose_call(
|
|
1350
|
+
call_module="Network",
|
|
1351
|
+
call_function="owner_update_delegate_stake_percentage",
|
|
1352
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
1353
|
+
)
|
|
1354
|
+
|
|
1355
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1356
|
+
|
|
1357
|
+
if response["success"]:
|
|
1358
|
+
response["message"] = (
|
|
1359
|
+
f"Subnet {request.subnet_id} delegate stake percentage updated successfully"
|
|
1360
|
+
)
|
|
1361
|
+
|
|
1362
|
+
return response
|
|
1363
|
+
|
|
1364
|
+
except Exception as e:
|
|
1365
|
+
logger.error(f"Error updating subnet delegate stake percentage: {e}")
|
|
1366
|
+
return {"success": False, "error": str(e)}
|
|
1367
|
+
|
|
1368
|
+
def owner_update_max_registered_nodes(
|
|
1369
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1370
|
+
) -> dict[str, Any]:
|
|
1371
|
+
"""
|
|
1372
|
+
Update subnet max registered nodes (owner only).
|
|
1373
|
+
|
|
1374
|
+
Args:
|
|
1375
|
+
request: Subnet max registered nodes update request
|
|
1376
|
+
keypair: Keypair for signing the transaction
|
|
1377
|
+
|
|
1378
|
+
Returns:
|
|
1379
|
+
dictionary with operation result
|
|
1380
|
+
"""
|
|
1381
|
+
try:
|
|
1382
|
+
logger.info(f"Updating subnet {request.subnet_id} max registered nodes")
|
|
1383
|
+
|
|
1384
|
+
call = self.substrate.compose_call(
|
|
1385
|
+
call_module="Network",
|
|
1386
|
+
call_function="owner_update_max_registered_nodes",
|
|
1387
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
1388
|
+
)
|
|
1389
|
+
|
|
1390
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1391
|
+
|
|
1392
|
+
if response["success"]:
|
|
1393
|
+
response["message"] = (
|
|
1394
|
+
f"Subnet {request.subnet_id} max registered nodes updated successfully"
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
return response
|
|
1398
|
+
|
|
1399
|
+
except Exception as e:
|
|
1400
|
+
logger.error(f"Error updating subnet max registered nodes: {e}")
|
|
1401
|
+
return {"success": False, "error": str(e)}
|
|
1402
|
+
|
|
1403
|
+
def owner_update_node_burn_rate_alpha(
|
|
1404
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1405
|
+
) -> dict[str, Any]:
|
|
1406
|
+
"""
|
|
1407
|
+
Update node burn rate alpha (owner only).
|
|
1408
|
+
|
|
1409
|
+
Args:
|
|
1410
|
+
request: Node burn rate alpha update request
|
|
1411
|
+
keypair: Keypair for signing the transaction
|
|
1412
|
+
|
|
1413
|
+
Returns:
|
|
1414
|
+
dictionary with operation result
|
|
1415
|
+
"""
|
|
1416
|
+
return self._execute_owner_value_update(
|
|
1417
|
+
request,
|
|
1418
|
+
keypair,
|
|
1419
|
+
call_function="owner_update_node_burn_rate_alpha",
|
|
1420
|
+
success_message=(
|
|
1421
|
+
"Subnet {subnet_id} node burn rate alpha updated successfully"
|
|
1422
|
+
),
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
def owner_update_subnet_node_min_weight_decrease_reputation_threshold(
|
|
1426
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1427
|
+
) -> dict[str, Any]:
|
|
1428
|
+
"""
|
|
1429
|
+
Update minimum weight decrease reputation threshold (owner only).
|
|
1430
|
+
|
|
1431
|
+
Args:
|
|
1432
|
+
request: Min weight decrease reputation threshold update request
|
|
1433
|
+
keypair: Keypair for signing the transaction
|
|
1434
|
+
|
|
1435
|
+
Returns:
|
|
1436
|
+
dictionary with operation result
|
|
1437
|
+
"""
|
|
1438
|
+
return self._execute_owner_value_update(
|
|
1439
|
+
request,
|
|
1440
|
+
keypair,
|
|
1441
|
+
call_function=(
|
|
1442
|
+
"owner_update_subnet_node_min_weight_decrease_reputation_threshold"
|
|
1443
|
+
),
|
|
1444
|
+
success_message=(
|
|
1445
|
+
"Subnet {subnet_id} min weight decrease reputation threshold updated successfully"
|
|
1446
|
+
),
|
|
1447
|
+
)
|
|
1448
|
+
|
|
1449
|
+
def owner_update_min_subnet_node_reputation(
|
|
1450
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1451
|
+
) -> dict[str, Any]:
|
|
1452
|
+
"""
|
|
1453
|
+
Update minimum subnet node reputation (owner only).
|
|
1454
|
+
|
|
1455
|
+
Args:
|
|
1456
|
+
request: Minimum subnet node reputation update request
|
|
1457
|
+
keypair: Keypair for signing the transaction
|
|
1458
|
+
|
|
1459
|
+
Returns:
|
|
1460
|
+
dictionary with operation result
|
|
1461
|
+
"""
|
|
1462
|
+
return self._execute_owner_value_update(
|
|
1463
|
+
request,
|
|
1464
|
+
keypair,
|
|
1465
|
+
call_function="owner_update_min_subnet_node_reputation",
|
|
1466
|
+
success_message=(
|
|
1467
|
+
"Subnet {subnet_id} minimum node reputation updated successfully"
|
|
1468
|
+
),
|
|
1469
|
+
)
|
|
1470
|
+
|
|
1471
|
+
def owner_update_absent_decrease_reputation_factor(
|
|
1472
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1473
|
+
) -> dict[str, Any]:
|
|
1474
|
+
"""
|
|
1475
|
+
Update absent decrease reputation factor (owner only).
|
|
1476
|
+
|
|
1477
|
+
Args:
|
|
1478
|
+
request: Absent decrease reputation factor update request
|
|
1479
|
+
keypair: Keypair for signing the transaction
|
|
1480
|
+
|
|
1481
|
+
Returns:
|
|
1482
|
+
dictionary with operation result
|
|
1483
|
+
"""
|
|
1484
|
+
return self._execute_owner_value_update(
|
|
1485
|
+
request,
|
|
1486
|
+
keypair,
|
|
1487
|
+
call_function="owner_update_absent_decrease_reputation_factor",
|
|
1488
|
+
success_message=(
|
|
1489
|
+
"Subnet {subnet_id} absent decrease reputation factor updated successfully"
|
|
1490
|
+
),
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1493
|
+
def owner_update_included_increase_reputation_factor(
|
|
1494
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1495
|
+
) -> dict[str, Any]:
|
|
1496
|
+
"""
|
|
1497
|
+
Update included increase reputation factor (owner only).
|
|
1498
|
+
|
|
1499
|
+
Args:
|
|
1500
|
+
request: Included increase reputation factor update request
|
|
1501
|
+
keypair: Keypair for signing the transaction
|
|
1502
|
+
|
|
1503
|
+
Returns:
|
|
1504
|
+
dictionary with operation result
|
|
1505
|
+
"""
|
|
1506
|
+
return self._execute_owner_value_update(
|
|
1507
|
+
request,
|
|
1508
|
+
keypair,
|
|
1509
|
+
call_function="owner_update_included_increase_reputation_factor",
|
|
1510
|
+
success_message=(
|
|
1511
|
+
"Subnet {subnet_id} included increase reputation factor updated successfully"
|
|
1512
|
+
),
|
|
1513
|
+
)
|
|
1514
|
+
|
|
1515
|
+
def owner_update_below_min_weight_decrease_reputation_factor(
|
|
1516
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1517
|
+
) -> dict[str, Any]:
|
|
1518
|
+
"""
|
|
1519
|
+
Update below-min weight decrease reputation factor (owner only).
|
|
1520
|
+
|
|
1521
|
+
Args:
|
|
1522
|
+
request: Below-min weight decrease reputation factor update request
|
|
1523
|
+
keypair: Keypair for signing the transaction
|
|
1524
|
+
|
|
1525
|
+
Returns:
|
|
1526
|
+
dictionary with operation result
|
|
1527
|
+
"""
|
|
1528
|
+
return self._execute_owner_value_update(
|
|
1529
|
+
request,
|
|
1530
|
+
keypair,
|
|
1531
|
+
call_function="owner_update_below_min_weight_decrease_reputation_factor",
|
|
1532
|
+
success_message=(
|
|
1533
|
+
"Subnet {subnet_id} below-min weight decrease reputation factor updated successfully"
|
|
1534
|
+
),
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1537
|
+
def owner_update_non_attestor_decrease_reputation_factor(
|
|
1538
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1539
|
+
) -> dict[str, Any]:
|
|
1540
|
+
"""
|
|
1541
|
+
Update non-attestor decrease reputation factor (owner only).
|
|
1542
|
+
|
|
1543
|
+
Args:
|
|
1544
|
+
request: Non-attestor decrease reputation factor update request
|
|
1545
|
+
keypair: Keypair for signing the transaction
|
|
1546
|
+
|
|
1547
|
+
Returns:
|
|
1548
|
+
dictionary with operation result
|
|
1549
|
+
"""
|
|
1550
|
+
return self._execute_owner_value_update(
|
|
1551
|
+
request,
|
|
1552
|
+
keypair,
|
|
1553
|
+
call_function="owner_update_non_attestor_decrease_reputation_factor",
|
|
1554
|
+
success_message=(
|
|
1555
|
+
"Subnet {subnet_id} non-attestor decrease reputation factor updated successfully"
|
|
1556
|
+
),
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
def owner_update_validator_absent_decrease_reputation_factor(
|
|
1560
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1561
|
+
) -> dict[str, Any]:
|
|
1562
|
+
"""
|
|
1563
|
+
Update validator absent decrease reputation factor (owner only).
|
|
1564
|
+
|
|
1565
|
+
Args:
|
|
1566
|
+
request: Validator absent decrease reputation factor update request
|
|
1567
|
+
keypair: Keypair for signing the transaction
|
|
1568
|
+
|
|
1569
|
+
Returns:
|
|
1570
|
+
dictionary with operation result
|
|
1571
|
+
"""
|
|
1572
|
+
return self._execute_owner_value_update(
|
|
1573
|
+
request,
|
|
1574
|
+
keypair,
|
|
1575
|
+
call_function="owner_update_validator_absent_decrease_reputation_factor",
|
|
1576
|
+
success_message=(
|
|
1577
|
+
"Subnet {subnet_id} validator absent decrease reputation factor updated successfully"
|
|
1578
|
+
),
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
def owner_update_validator_non_consensus_decrease_reputation_factor(
|
|
1582
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1583
|
+
) -> dict[str, Any]:
|
|
1584
|
+
"""
|
|
1585
|
+
Update validator non-consensus decrease reputation factor (owner only).
|
|
1586
|
+
|
|
1587
|
+
Args:
|
|
1588
|
+
request: Validator non-consensus decrease reputation factor update request
|
|
1589
|
+
keypair: Keypair for signing the transaction
|
|
1590
|
+
|
|
1591
|
+
Returns:
|
|
1592
|
+
dictionary with operation result
|
|
1593
|
+
"""
|
|
1594
|
+
return self._execute_owner_value_update(
|
|
1595
|
+
request,
|
|
1596
|
+
keypair,
|
|
1597
|
+
call_function="owner_update_validator_non_consensus_decrease_reputation_factor",
|
|
1598
|
+
success_message=(
|
|
1599
|
+
"Subnet {subnet_id} validator non-consensus decrease reputation factor updated successfully"
|
|
1600
|
+
),
|
|
1601
|
+
)
|
|
1602
|
+
|
|
1603
|
+
def owner_update_non_consensus_attestor_decrease_reputation_factor(
|
|
1604
|
+
self, request: SubnetConfigUpdateRequest, keypair: Keypair
|
|
1605
|
+
) -> dict[str, Any]:
|
|
1606
|
+
"""
|
|
1607
|
+
Update non-consensus attestor decrease reputation factor (owner only).
|
|
1608
|
+
|
|
1609
|
+
Args:
|
|
1610
|
+
request: Non-consensus attestor decrease reputation factor update request
|
|
1611
|
+
keypair: Keypair for signing the transaction
|
|
1612
|
+
|
|
1613
|
+
Returns:
|
|
1614
|
+
dictionary with operation result
|
|
1615
|
+
"""
|
|
1616
|
+
return self._execute_owner_value_update(
|
|
1617
|
+
request,
|
|
1618
|
+
keypair,
|
|
1619
|
+
call_function="owner_update_non_consensus_attestor_decrease_reputation_factor",
|
|
1620
|
+
success_message=(
|
|
1621
|
+
"Subnet {subnet_id} non-consensus attestor decrease reputation factor updated successfully"
|
|
1622
|
+
),
|
|
1623
|
+
)
|
|
1624
|
+
|
|
1625
|
+
# ============================================================================
|
|
1626
|
+
# SUBNET OWNERSHIP TRANSFER
|
|
1627
|
+
# ============================================================================
|
|
1628
|
+
|
|
1629
|
+
def transfer_subnet_ownership(
|
|
1630
|
+
self, request: SubnetOwnershipTransferRequest, keypair: Keypair
|
|
1631
|
+
) -> dict[str, Any]:
|
|
1632
|
+
"""
|
|
1633
|
+
Transfer subnet ownership.
|
|
1634
|
+
|
|
1635
|
+
Args:
|
|
1636
|
+
request: Subnet ownership transfer request
|
|
1637
|
+
keypair: Keypair for signing the transaction
|
|
1638
|
+
|
|
1639
|
+
Returns:
|
|
1640
|
+
dictionary with operation result
|
|
1641
|
+
"""
|
|
1642
|
+
try:
|
|
1643
|
+
logger.info(
|
|
1644
|
+
f"Transferring subnet {request.subnet_id} ownership to {request.new_owner}"
|
|
1645
|
+
)
|
|
1646
|
+
|
|
1647
|
+
call = self.substrate.compose_call(
|
|
1648
|
+
call_module="Network",
|
|
1649
|
+
call_function="transfer_subnet_ownership",
|
|
1650
|
+
call_params={
|
|
1651
|
+
"subnet_id": request.subnet_id,
|
|
1652
|
+
"new_owner": request.new_owner,
|
|
1653
|
+
},
|
|
1654
|
+
)
|
|
1655
|
+
|
|
1656
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1657
|
+
|
|
1658
|
+
if response["success"]:
|
|
1659
|
+
response["message"] = (
|
|
1660
|
+
f"Subnet {request.subnet_id} ownership transfer initiated successfully"
|
|
1661
|
+
)
|
|
1662
|
+
|
|
1663
|
+
return response
|
|
1664
|
+
|
|
1665
|
+
except Exception as e:
|
|
1666
|
+
logger.error(f"Error transferring subnet ownership: {e}")
|
|
1667
|
+
return {"success": False, "error": str(e)}
|
|
1668
|
+
|
|
1669
|
+
def accept_subnet_ownership(
|
|
1670
|
+
self, request: SubnetOwnershipAcceptRequest, keypair: Keypair
|
|
1671
|
+
) -> dict[str, Any]:
|
|
1672
|
+
"""
|
|
1673
|
+
Accept subnet ownership.
|
|
1674
|
+
|
|
1675
|
+
Args:
|
|
1676
|
+
request: Subnet ownership accept request
|
|
1677
|
+
keypair: Keypair for signing the transaction
|
|
1678
|
+
|
|
1679
|
+
Returns:
|
|
1680
|
+
dictionary with operation result
|
|
1681
|
+
"""
|
|
1682
|
+
try:
|
|
1683
|
+
logger.info(f"Accepting subnet {request.subnet_id} ownership")
|
|
1684
|
+
|
|
1685
|
+
call = self.substrate.compose_call(
|
|
1686
|
+
call_module="Network",
|
|
1687
|
+
call_function="accept_subnet_ownership",
|
|
1688
|
+
call_params={"subnet_id": request.subnet_id},
|
|
1689
|
+
)
|
|
1690
|
+
|
|
1691
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1692
|
+
|
|
1693
|
+
if response["success"]:
|
|
1694
|
+
response["message"] = (
|
|
1695
|
+
f"Subnet {request.subnet_id} ownership accepted successfully"
|
|
1696
|
+
)
|
|
1697
|
+
|
|
1698
|
+
return response
|
|
1699
|
+
|
|
1700
|
+
except Exception as e:
|
|
1701
|
+
logger.error(f"Error accepting subnet ownership: {e}")
|
|
1702
|
+
return {"success": False, "error": str(e)}
|
|
1703
|
+
|
|
1704
|
+
# ============================================================================
|
|
1705
|
+
# SUBNET EMERGENCY CONTROLS
|
|
1706
|
+
# ============================================================================
|
|
1707
|
+
|
|
1708
|
+
def owner_set_emergency_validator_set(
|
|
1709
|
+
self, subnet_id: int, subnet_node_ids: list[int], keypair: Keypair
|
|
1710
|
+
) -> dict[str, Any]:
|
|
1711
|
+
"""
|
|
1712
|
+
Override the validator set for a subnet (owner only).
|
|
1713
|
+
|
|
1714
|
+
Args:
|
|
1715
|
+
subnet_id: The subnet ID
|
|
1716
|
+
subnet_node_ids: List of subnet node IDs to be used as validators
|
|
1717
|
+
keypair: Keypair for signing the transaction
|
|
1718
|
+
|
|
1719
|
+
Returns:
|
|
1720
|
+
dictionary with operation result
|
|
1721
|
+
"""
|
|
1722
|
+
try:
|
|
1723
|
+
sorted_ids = sorted(subnet_node_ids)
|
|
1724
|
+
logger.info(
|
|
1725
|
+
f"Setting emergency validator set for subnet {subnet_id}: {sorted_ids}"
|
|
1726
|
+
)
|
|
1727
|
+
|
|
1728
|
+
call = self.substrate.compose_call(
|
|
1729
|
+
call_module="Network",
|
|
1730
|
+
call_function="owner_set_emergency_validator_set",
|
|
1731
|
+
call_params={"subnet_id": subnet_id, "subnet_node_ids": sorted_ids},
|
|
1732
|
+
)
|
|
1733
|
+
|
|
1734
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1735
|
+
|
|
1736
|
+
if response["success"]:
|
|
1737
|
+
response["message"] = (
|
|
1738
|
+
f"Subnet {subnet_id} emergency validator set updated successfully"
|
|
1739
|
+
)
|
|
1740
|
+
|
|
1741
|
+
return response
|
|
1742
|
+
|
|
1743
|
+
except Exception as e:
|
|
1744
|
+
logger.error(
|
|
1745
|
+
f"Error setting emergency validator set for subnet {subnet_id}: {e}"
|
|
1746
|
+
)
|
|
1747
|
+
return {"success": False, "error": str(e)}
|
|
1748
|
+
|
|
1749
|
+
def owner_add_bootnode_access(
|
|
1750
|
+
self, subnet_id: int, new_account: str, keypair: Keypair
|
|
1751
|
+
) -> dict[str, Any]:
|
|
1752
|
+
"""
|
|
1753
|
+
Grant bootnode access to an account (owner only).
|
|
1754
|
+
|
|
1755
|
+
Args:
|
|
1756
|
+
subnet_id: The subnet ID
|
|
1757
|
+
new_account: Account ID to add
|
|
1758
|
+
keypair: Keypair for signing the transaction
|
|
1759
|
+
|
|
1760
|
+
Returns:
|
|
1761
|
+
dictionary with operation result
|
|
1762
|
+
"""
|
|
1763
|
+
try:
|
|
1764
|
+
validate_address(new_account)
|
|
1765
|
+
logger.info(
|
|
1766
|
+
f"Adding bootnode access for subnet {subnet_id} to {new_account}"
|
|
1767
|
+
)
|
|
1768
|
+
|
|
1769
|
+
call = self.substrate.compose_call(
|
|
1770
|
+
call_module="Network",
|
|
1771
|
+
call_function="owner_add_bootnode_access",
|
|
1772
|
+
call_params={"subnet_id": subnet_id, "new_account": new_account},
|
|
1773
|
+
)
|
|
1774
|
+
|
|
1775
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1776
|
+
|
|
1777
|
+
if response["success"]:
|
|
1778
|
+
response["message"] = (
|
|
1779
|
+
f"Subnet {subnet_id} bootnode access granted to {new_account}"
|
|
1780
|
+
)
|
|
1781
|
+
|
|
1782
|
+
return response
|
|
1783
|
+
|
|
1784
|
+
except Exception as e:
|
|
1785
|
+
logger.error(f"Error adding bootnode access for subnet {subnet_id}: {e}")
|
|
1786
|
+
return {"success": False, "error": str(e)}
|
|
1787
|
+
|
|
1788
|
+
def owner_remove_bootnode_access(
|
|
1789
|
+
self, subnet_id: int, remove_account: str, keypair: Keypair
|
|
1790
|
+
) -> dict[str, Any]:
|
|
1791
|
+
"""
|
|
1792
|
+
Remove bootnode access from an account (owner only).
|
|
1793
|
+
|
|
1794
|
+
Args:
|
|
1795
|
+
subnet_id: The subnet ID
|
|
1796
|
+
remove_account: Account ID to remove
|
|
1797
|
+
keypair: Keypair for signing the transaction
|
|
1798
|
+
|
|
1799
|
+
Returns:
|
|
1800
|
+
dictionary with operation result
|
|
1801
|
+
"""
|
|
1802
|
+
try:
|
|
1803
|
+
validate_address(remove_account)
|
|
1804
|
+
logger.info(
|
|
1805
|
+
f"Removing bootnode access for subnet {subnet_id} from {remove_account}"
|
|
1806
|
+
)
|
|
1807
|
+
|
|
1808
|
+
call = self.substrate.compose_call(
|
|
1809
|
+
call_module="Network",
|
|
1810
|
+
call_function="owner_remove_bootnode_access",
|
|
1811
|
+
call_params={"subnet_id": subnet_id, "remove_account": remove_account},
|
|
1812
|
+
)
|
|
1813
|
+
|
|
1814
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1815
|
+
|
|
1816
|
+
if response["success"]:
|
|
1817
|
+
response["message"] = (
|
|
1818
|
+
f"Subnet {subnet_id} bootnode access revoked from {remove_account}"
|
|
1819
|
+
)
|
|
1820
|
+
|
|
1821
|
+
return response
|
|
1822
|
+
|
|
1823
|
+
except Exception as e:
|
|
1824
|
+
logger.error(f"Error removing bootnode access for subnet {subnet_id}: {e}")
|
|
1825
|
+
return {"success": False, "error": str(e)}
|
|
1826
|
+
|
|
1827
|
+
# ============================================================================
|
|
1828
|
+
# BOOTNODE MANAGEMENT
|
|
1829
|
+
# ============================================================================
|
|
1830
|
+
|
|
1831
|
+
def update_bootnodes(
|
|
1832
|
+
self,
|
|
1833
|
+
subnet_id: int,
|
|
1834
|
+
add_bootnodes: list[str],
|
|
1835
|
+
remove_bootnodes: list[str],
|
|
1836
|
+
keypair: Keypair,
|
|
1837
|
+
) -> dict[str, Any]:
|
|
1838
|
+
"""
|
|
1839
|
+
Update subnet bootnodes.
|
|
1840
|
+
|
|
1841
|
+
Args:
|
|
1842
|
+
subnet_id: The subnet ID
|
|
1843
|
+
add_bootnodes: list of bootnode addresses to add
|
|
1844
|
+
remove_bootnodes: list of bootnode addresses to remove
|
|
1845
|
+
keypair: Keypair for signing the transaction
|
|
1846
|
+
|
|
1847
|
+
Returns:
|
|
1848
|
+
dictionary with operation result
|
|
1849
|
+
"""
|
|
1850
|
+
try:
|
|
1851
|
+
logger.info(f"Updating subnet {subnet_id} bootnodes")
|
|
1852
|
+
|
|
1853
|
+
# Encode bootnode addresses to bytes
|
|
1854
|
+
add_bootnodes_encoded = [addr.encode("utf-8") for addr in add_bootnodes]
|
|
1855
|
+
remove_bootnodes_encoded = [
|
|
1856
|
+
addr.encode("utf-8") for addr in remove_bootnodes
|
|
1857
|
+
]
|
|
1858
|
+
|
|
1859
|
+
call = self.substrate.compose_call(
|
|
1860
|
+
call_module="Network",
|
|
1861
|
+
call_function="update_bootnodes",
|
|
1862
|
+
call_params={
|
|
1863
|
+
"subnet_id": subnet_id,
|
|
1864
|
+
"add": add_bootnodes_encoded,
|
|
1865
|
+
"remove": remove_bootnodes_encoded,
|
|
1866
|
+
},
|
|
1867
|
+
)
|
|
1868
|
+
|
|
1869
|
+
response = self._submit_extrinsic(call, keypair)
|
|
1870
|
+
|
|
1871
|
+
if response["success"]:
|
|
1872
|
+
response["message"] = (
|
|
1873
|
+
f"Subnet {subnet_id} bootnodes updated successfully"
|
|
1874
|
+
)
|
|
1875
|
+
|
|
1876
|
+
return response
|
|
1877
|
+
|
|
1878
|
+
except Exception as e:
|
|
1879
|
+
logger.error(f"Error updating bootnodes: {e}")
|
|
1880
|
+
return {"success": False, "error": str(e)}
|
|
1881
|
+
|
|
1882
|
+
# ============================================================================
|
|
1883
|
+
# QUERY METHODS
|
|
1884
|
+
# ============================================================================
|
|
1885
|
+
|
|
1886
|
+
def get_subnet_info(self, subnet_id: int) -> Optional[SubnetInfo]:
|
|
1887
|
+
"""Delegate to RPC layer for comprehensive subnet info."""
|
|
1888
|
+
from ..rpc.subnet import SubnetRpcClient
|
|
1889
|
+
|
|
1890
|
+
try:
|
|
1891
|
+
rpc_client = SubnetRpcClient(self.substrate)
|
|
1892
|
+
return rpc_client.get_subnet_info(subnet_id)
|
|
1893
|
+
except Exception as e:
|
|
1894
|
+
logger.error(f"Error getting subnet info: {e}")
|
|
1895
|
+
return None
|
|
1896
|
+
|
|
1897
|
+
def get_current_registration_cost(self) -> int:
|
|
1898
|
+
"""Get the current subnet registration cost."""
|
|
1899
|
+
return self._get_current_registration_cost()
|
|
1900
|
+
|
|
1901
|
+
def list_subnets(self) -> list[SubnetInfo]:
|
|
1902
|
+
"""
|
|
1903
|
+
list all registered subnets.
|
|
1904
|
+
|
|
1905
|
+
Returns:
|
|
1906
|
+
list of SubnetInfo objects
|
|
1907
|
+
"""
|
|
1908
|
+
try:
|
|
1909
|
+
subnets = []
|
|
1910
|
+
|
|
1911
|
+
# Get total subnet count
|
|
1912
|
+
total_subnets = (
|
|
1913
|
+
self.substrate.query(
|
|
1914
|
+
module="Network", storage_function="TotalSubnetUids"
|
|
1915
|
+
).value
|
|
1916
|
+
or 0
|
|
1917
|
+
)
|
|
1918
|
+
|
|
1919
|
+
# Query each subnet
|
|
1920
|
+
for subnet_id in range(1, total_subnets + 1):
|
|
1921
|
+
subnet_info = self.get_subnet_info(subnet_id)
|
|
1922
|
+
if subnet_info:
|
|
1923
|
+
subnets.append(subnet_info)
|
|
1924
|
+
|
|
1925
|
+
return subnets
|
|
1926
|
+
|
|
1927
|
+
except Exception as e:
|
|
1928
|
+
logger.error(f"Error listing subnets: {e}")
|
|
1929
|
+
return []
|
|
1930
|
+
|
|
1931
|
+
# ============================================================================
|
|
1932
|
+
# PRIVATE HELPER METHODS
|
|
1933
|
+
# ============================================================================
|
|
1934
|
+
|
|
1935
|
+
def _validate_registration_request(self, request: SubnetRegisterRequest):
|
|
1936
|
+
"""Validate the registration request."""
|
|
1937
|
+
# Validate initial coldkeys
|
|
1938
|
+
for coldkey in request.initial_coldkeys:
|
|
1939
|
+
if not validate_address(coldkey):
|
|
1940
|
+
raise ValueError(f"Invalid coldkey address format: {coldkey}")
|
|
1941
|
+
|
|
1942
|
+
# Validate bootnodes format
|
|
1943
|
+
# Bootnodes is a set[str] in the model (matches BTreeSet in Rust)
|
|
1944
|
+
# Ensure it's not None and filter out any None values
|
|
1945
|
+
if request.bootnodes is None:
|
|
1946
|
+
request.bootnodes = set() # Default to empty set if None
|
|
1947
|
+
else:
|
|
1948
|
+
# Filter out None values from the set (shouldn't happen, but be safe)
|
|
1949
|
+
request.bootnodes = {
|
|
1950
|
+
bn for bn in request.bootnodes if bn is not None and isinstance(bn, str)
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
for bootnode in request.bootnodes:
|
|
1954
|
+
if not isinstance(bootnode, str):
|
|
1955
|
+
raise ValueError(f"Bootnode must be a string, got {type(bootnode)}")
|
|
1956
|
+
if not bootnode.strip():
|
|
1957
|
+
raise ValueError("Bootnode cannot be empty")
|
|
1958
|
+
|
|
1959
|
+
def _prepare_subnet_data(self, request: SubnetRegisterRequest) -> dict[str, Any]:
|
|
1960
|
+
"""
|
|
1961
|
+
Prepare subnet data for the extrinsic call.
|
|
1962
|
+
|
|
1963
|
+
RegistrationSubnetData structure (from Rust):
|
|
1964
|
+
- name: Vec<u8>
|
|
1965
|
+
- repo: Vec<u8>
|
|
1966
|
+
- description: Vec<u8>
|
|
1967
|
+
- misc: Vec<u8>
|
|
1968
|
+
- min_stake: u128
|
|
1969
|
+
- max_stake: u128
|
|
1970
|
+
- delegate_stake_percentage: u128
|
|
1971
|
+
- initial_validators: BTreeMap<u32, u32> (validator_id -> max_registrations)
|
|
1972
|
+
- bootnodes: BTreeMap<PeerId, BoundedVec<u8, ...>>
|
|
1973
|
+
"""
|
|
1974
|
+
|
|
1975
|
+
def encode_validator_limits(accounts: dict[int, int]) -> list[tuple[int, int]]:
|
|
1976
|
+
encoded = []
|
|
1977
|
+
for validator_id, max_registrations in accounts.items():
|
|
1978
|
+
final_max_registrations = max(max_registrations, 1)
|
|
1979
|
+
encoded.append((int(validator_id), final_max_registrations))
|
|
1980
|
+
|
|
1981
|
+
encoded.sort(key=lambda item: item[0])
|
|
1982
|
+
return encoded
|
|
1983
|
+
|
|
1984
|
+
def encode_bootnodes(bootnodes: set[str]) -> list[tuple[bytes, bytes]]:
|
|
1985
|
+
encoded = []
|
|
1986
|
+
for bootnode in bootnodes:
|
|
1987
|
+
if (
|
|
1988
|
+
bootnode is None
|
|
1989
|
+
or not isinstance(bootnode, str)
|
|
1990
|
+
or not bootnode.strip()
|
|
1991
|
+
):
|
|
1992
|
+
continue
|
|
1993
|
+
|
|
1994
|
+
parts = bootnode.rsplit("/p2p/", 1)
|
|
1995
|
+
if len(parts) != 2 or not parts[1].strip():
|
|
1996
|
+
raise ValueError(
|
|
1997
|
+
f"Bootnode must include a peer id using /p2p/<peer_id>: {bootnode}"
|
|
1998
|
+
)
|
|
1999
|
+
|
|
2000
|
+
multiaddr = bootnode.strip()
|
|
2001
|
+
peer_id = parts[1].strip()
|
|
2002
|
+
if not multiaddr:
|
|
2003
|
+
raise ValueError(f"Bootnode multiaddr cannot be empty: {bootnode}")
|
|
2004
|
+
|
|
2005
|
+
encoded.append((encode_peer_id(peer_id), _encode_multiaddr(multiaddr)))
|
|
2006
|
+
|
|
2007
|
+
encoded.sort(key=lambda item: item[0])
|
|
2008
|
+
return encoded
|
|
2009
|
+
|
|
2010
|
+
initial_validators_list = encode_validator_limits(request.initial_validators)
|
|
2011
|
+
bootnodes_list = encode_bootnodes(request.bootnodes or set())
|
|
2012
|
+
|
|
2013
|
+
return {
|
|
2014
|
+
"name": request.name.encode("utf-8") if request.name else b"",
|
|
2015
|
+
"repo": request.repo.encode("utf-8") if request.repo else b"",
|
|
2016
|
+
"description": (
|
|
2017
|
+
request.description.encode("utf-8") if request.description else b""
|
|
2018
|
+
),
|
|
2019
|
+
"misc": request.misc.encode("utf-8") if request.misc else b"",
|
|
2020
|
+
"min_stake": request.min_stake,
|
|
2021
|
+
"max_stake": request.max_stake,
|
|
2022
|
+
"delegate_stake_percentage": request.delegate_stake_percentage,
|
|
2023
|
+
"initial_validators": initial_validators_list,
|
|
2024
|
+
"bootnodes": bootnodes_list,
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
def _get_current_registration_cost(self) -> int:
|
|
2028
|
+
"""
|
|
2029
|
+
Get the current registration cost from the chain.
|
|
2030
|
+
|
|
2031
|
+
Uses ChainRpcClient to query storage and calculate current cost.
|
|
2032
|
+
Falls back to a safe default if query fails.
|
|
2033
|
+
"""
|
|
2034
|
+
try:
|
|
2035
|
+
from ..rpc import ChainRpcClient
|
|
2036
|
+
|
|
2037
|
+
chain_rpc = ChainRpcClient(self.substrate)
|
|
2038
|
+
cost = chain_rpc.get_subnet_registration_cost()
|
|
2039
|
+
|
|
2040
|
+
if cost is not None:
|
|
2041
|
+
logger.info(f"Current registration cost: {cost / 1e18:.2f} TENSOR")
|
|
2042
|
+
return cost
|
|
2043
|
+
|
|
2044
|
+
# Fallback: return high value to ensure it passes
|
|
2045
|
+
logger.warning("Could not get registration cost, using default 100 TENSOR")
|
|
2046
|
+
return int(100 * 1e18)
|
|
2047
|
+
except Exception as e:
|
|
2048
|
+
logger.warning(
|
|
2049
|
+
f"Error getting registration cost: {e}, using default 100 TENSOR"
|
|
2050
|
+
)
|
|
2051
|
+
# Return safe default
|
|
2052
|
+
return int(100 * 1e18)
|
|
2053
|
+
|
|
2054
|
+
def _get_current_epoch(self) -> int:
|
|
2055
|
+
"""Get the current epoch number."""
|
|
2056
|
+
try:
|
|
2057
|
+
block_header = self.substrate.get_block_header()
|
|
2058
|
+
current_block = block_header.get("number", 0) if block_header else 0
|
|
2059
|
+
epoch_length = (
|
|
2060
|
+
self.substrate.query(
|
|
2061
|
+
module="Network", storage_function="EpochLength"
|
|
2062
|
+
).value
|
|
2063
|
+
or 300
|
|
2064
|
+
)
|
|
2065
|
+
|
|
2066
|
+
return current_block // epoch_length
|
|
2067
|
+
except Exception as e:
|
|
2068
|
+
logger.warning(f"Could not get current epoch: {e}")
|
|
2069
|
+
return 0
|
|
2070
|
+
|
|
2071
|
+
def _execute_owner_value_update(
|
|
2072
|
+
self,
|
|
2073
|
+
request: SubnetConfigUpdateRequest,
|
|
2074
|
+
keypair: Keypair,
|
|
2075
|
+
call_function: str,
|
|
2076
|
+
success_message: str,
|
|
2077
|
+
) -> dict[str, Any]:
|
|
2078
|
+
"""
|
|
2079
|
+
Helper to execute owner-only configuration updates with a single value.
|
|
2080
|
+
|
|
2081
|
+
Args:
|
|
2082
|
+
request: Configuration update request with subnet_id and value
|
|
2083
|
+
keypair: Keypair for signing the transaction
|
|
2084
|
+
call_function: Runtime call to execute
|
|
2085
|
+
success_message: Message to attach upon success
|
|
2086
|
+
|
|
2087
|
+
Returns:
|
|
2088
|
+
dictionary with operation result
|
|
2089
|
+
"""
|
|
2090
|
+
try:
|
|
2091
|
+
logger.info(
|
|
2092
|
+
f"Calling {call_function} for subnet {request.subnet_id} with value {request.value}"
|
|
2093
|
+
)
|
|
2094
|
+
|
|
2095
|
+
call = self.substrate.compose_call(
|
|
2096
|
+
call_module="Network",
|
|
2097
|
+
call_function=call_function,
|
|
2098
|
+
call_params={"subnet_id": request.subnet_id, "value": request.value},
|
|
2099
|
+
)
|
|
2100
|
+
|
|
2101
|
+
response = self._submit_extrinsic(call, keypair)
|
|
2102
|
+
|
|
2103
|
+
if response.get("success"):
|
|
2104
|
+
response["message"] = success_message.format(
|
|
2105
|
+
subnet_id=request.subnet_id
|
|
2106
|
+
)
|
|
2107
|
+
|
|
2108
|
+
return response
|
|
2109
|
+
|
|
2110
|
+
except Exception as e:
|
|
2111
|
+
logger.error(
|
|
2112
|
+
f"Error executing {call_function} for subnet {request.subnet_id}: {e}"
|
|
2113
|
+
)
|
|
2114
|
+
return {"success": False, "error": str(e)}
|
|
2115
|
+
|
|
2116
|
+
# _submit_extrinsic method is now inherited from BaseExtrinsicClient
|
|
2117
|
+
|
|
2118
|
+
def _extract_registration_event(self, response) -> dict[str, Any]:
|
|
2119
|
+
"""Extract subnet registration event data from the response."""
|
|
2120
|
+
event_data = {}
|
|
2121
|
+
|
|
2122
|
+
try:
|
|
2123
|
+
for event in response.triggered_events:
|
|
2124
|
+
if (
|
|
2125
|
+
event.value["module_id"] == "Network"
|
|
2126
|
+
and event.value["event_id"] == "SubnetRegistered"
|
|
2127
|
+
):
|
|
2128
|
+
attributes = event.value.get("attributes", [])
|
|
2129
|
+
# SubnetRegistered event has 3 attributes: [owner, name, subnet_id]
|
|
2130
|
+
if isinstance(attributes, list) and len(attributes) >= 3:
|
|
2131
|
+
# Handle name which might be bytes
|
|
2132
|
+
name_value = attributes[1]
|
|
2133
|
+
if isinstance(name_value, bytes):
|
|
2134
|
+
name_str = name_value.decode("utf-8")
|
|
2135
|
+
elif (
|
|
2136
|
+
isinstance(name_value, (list, tuple))
|
|
2137
|
+
and len(name_value) > 0
|
|
2138
|
+
):
|
|
2139
|
+
name_str = (
|
|
2140
|
+
name_value[0].decode("utf-8")
|
|
2141
|
+
if isinstance(name_value[0], bytes)
|
|
2142
|
+
else str(name_value[0])
|
|
2143
|
+
)
|
|
2144
|
+
else:
|
|
2145
|
+
name_str = str(name_value)
|
|
2146
|
+
|
|
2147
|
+
event_data = {
|
|
2148
|
+
"subnet_id": attributes[2], # subnet_id
|
|
2149
|
+
"owner": attributes[0], # owner
|
|
2150
|
+
"name": name_str, # name
|
|
2151
|
+
}
|
|
2152
|
+
break
|
|
2153
|
+
except Exception as e:
|
|
2154
|
+
logger.warning(f"Could not extract event data: {e}")
|
|
2155
|
+
|
|
2156
|
+
return event_data
|
|
2157
|
+
|
|
2158
|
+
def _extract_registration_event_from_result(
|
|
2159
|
+
self, result: dict[str, Any]
|
|
2160
|
+
) -> dict[str, Any]:
|
|
2161
|
+
"""Extract subnet registration event data from result dict."""
|
|
2162
|
+
event_data = {}
|
|
2163
|
+
|
|
2164
|
+
try:
|
|
2165
|
+
events = result.get("events", [])
|
|
2166
|
+
logger.debug(f"Extracting from {len(events)} events")
|
|
2167
|
+
|
|
2168
|
+
for idx, event in enumerate(events):
|
|
2169
|
+
logger.debug(f"Event {idx}: {type(event)}")
|
|
2170
|
+
|
|
2171
|
+
# Check if event has value attribute
|
|
2172
|
+
if hasattr(event, "value"):
|
|
2173
|
+
event_value = event.value
|
|
2174
|
+
module_id = event_value.get("module_id", "")
|
|
2175
|
+
event_id = event_value.get("event_id", "")
|
|
2176
|
+
|
|
2177
|
+
if module_id == "Network" and event_id == "SubnetRegistered":
|
|
2178
|
+
attributes = event_value.get("attributes", {})
|
|
2179
|
+
|
|
2180
|
+
# Handle both dict and list formats
|
|
2181
|
+
if isinstance(attributes, dict):
|
|
2182
|
+
event_data = {
|
|
2183
|
+
"subnet_id": attributes.get("subnet_id"),
|
|
2184
|
+
"owner": attributes.get("owner"),
|
|
2185
|
+
"name": attributes.get("name"),
|
|
2186
|
+
}
|
|
2187
|
+
elif isinstance(attributes, list) and len(attributes) >= 3:
|
|
2188
|
+
# Handle name which might be bytes or a tuple
|
|
2189
|
+
name_value = attributes[1]
|
|
2190
|
+
if isinstance(name_value, bytes):
|
|
2191
|
+
name_str = name_value.decode("utf-8")
|
|
2192
|
+
elif (
|
|
2193
|
+
isinstance(name_value, (list, tuple))
|
|
2194
|
+
and len(name_value) > 0
|
|
2195
|
+
):
|
|
2196
|
+
# If it's a tuple/list, take the first element
|
|
2197
|
+
name_str = (
|
|
2198
|
+
name_value[0].decode("utf-8")
|
|
2199
|
+
if isinstance(name_value[0], bytes)
|
|
2200
|
+
else str(name_value[0])
|
|
2201
|
+
)
|
|
2202
|
+
else:
|
|
2203
|
+
name_str = str(name_value)
|
|
2204
|
+
|
|
2205
|
+
event_data = {
|
|
2206
|
+
"subnet_id": attributes[2],
|
|
2207
|
+
"owner": attributes[0],
|
|
2208
|
+
"name": name_str,
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
break
|
|
2212
|
+
|
|
2213
|
+
except Exception as e:
|
|
2214
|
+
logger.warning(f"Event extraction error: {e}")
|
|
2215
|
+
|
|
2216
|
+
return event_data
|
|
2217
|
+
|
|
2218
|
+
# _extract_error_message method is now inherited from BaseExtrinsicClient
|