Skip to content

Commit d2c6642

Browse files
committed
Merge branch 'SDKv10' into docstrings-updates-for-v10
2 parents 83e869a + 950d8d6 commit d2c6642

File tree

14 files changed

+636
-56
lines changed

14 files changed

+636
-56
lines changed

bittensor/core/async_subtensor.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3915,7 +3915,7 @@ async def get_stake_info_for_coldkey(
39153915
block: Optional[int] = None,
39163916
block_hash: Optional[str] = None,
39173917
reuse_block: bool = False,
3918-
) -> Optional[list["StakeInfo"]]:
3918+
) -> list["StakeInfo"]:
39193919
"""
39203920
Retrieves the stake information for a given coldkey.
39213921
@@ -3926,7 +3926,7 @@ async def get_stake_info_for_coldkey(
39263926
reuse_block: Whether to reuse the last-used block hash.
39273927
39283928
Returns:
3929-
An optional list of StakeInfo objects, or ``None`` if no stake information is found.
3929+
List of StakeInfo objects.
39303930
"""
39313931
result = await self.query_runtime_api(
39323932
runtime_api="StakeInfoRuntimeApi",
@@ -3943,6 +3943,42 @@ async def get_stake_info_for_coldkey(
39433943
stakes: list[StakeInfo] = StakeInfo.list_from_dicts(result)
39443944
return [stake for stake in stakes if stake.stake > 0]
39453945

3946+
async def get_stake_info_for_coldkeys(
3947+
self,
3948+
coldkey_ss58s: list[str],
3949+
block: Optional[int] = None,
3950+
block_hash: Optional[str] = None,
3951+
reuse_block: bool = False,
3952+
) -> dict[str, list["StakeInfo"]]:
3953+
"""
3954+
Retrieves the stake information for multiple coldkeys.
3955+
3956+
Parameters:
3957+
coldkey_ss58s: A list of SS58 addresses of the coldkeys to query.
3958+
block: The block number at which to query the stake information.
3959+
block_hash: The hash of the blockchain block number for the query.
3960+
reuse_block: Whether to reuse the last-used block hash.
3961+
3962+
Returns:
3963+
The dictionary mapping coldkey addresses to a list of StakeInfo objects.
3964+
"""
3965+
query = await self.query_runtime_api(
3966+
runtime_api="StakeInfoRuntimeApi",
3967+
method="get_stake_info_for_coldkeys",
3968+
params=[coldkey_ss58s],
3969+
block=block,
3970+
block_hash=block_hash,
3971+
reuse_block=reuse_block,
3972+
)
3973+
3974+
if query is None:
3975+
return {}
3976+
3977+
return {
3978+
decode_account_id(ck): StakeInfo.list_from_dicts(st_info)
3979+
for ck, st_info in query
3980+
}
3981+
39463982
async def get_stake_for_hotkey(
39473983
self,
39483984
hotkey_ss58: str,
@@ -5619,7 +5655,7 @@ async def sign_and_send_extrinsic(
56195655
wait_for_inclusion=wait_for_inclusion,
56205656
wait_for_finalization=wait_for_finalization,
56215657
)
5622-
extrinsic_response.extrinsic_receipt = response
5658+
56235659
# We only wait here if we expect finalization.
56245660
if not wait_for_finalization and not wait_for_inclusion:
56255661
extrinsic_response.extrinsic_fee = await self.get_extrinsic_fee(
@@ -5631,6 +5667,8 @@ async def sign_and_send_extrinsic(
56315667
logging.debug(extrinsic_response.message)
56325668
return extrinsic_response
56335669

5670+
extrinsic_response.extrinsic_receipt = response
5671+
56345672
if await response.is_success:
56355673
extrinsic_response.extrinsic_fee = Balance.from_rao(
56365674
await response.total_fee_amount

bittensor/core/extrinsics/asyncex/proxy.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import asyncio
12
from typing import TYPE_CHECKING, Optional, Union
23

34
from bittensor.core.chain_data.proxy import ProxyType
45
from bittensor.core.extrinsics.pallets import Proxy
6+
from bittensor.core.extrinsics.utils import apply_pure_proxy_data
57
from bittensor.core.types import ExtrinsicResponse
68
from bittensor.utils.btlogging import logging
79

@@ -42,6 +44,13 @@ async def add_proxy_extrinsic(
4244
4345
Returns:
4446
ExtrinsicResponse: The result object of the extrinsic execution.
47+
48+
Warning:
49+
If ``wait_for_inclusion=False`` or when ``block_hash`` is not available, the extrinsic receipt may not contain
50+
triggered events. This means that any data that would normally be extracted from blockchain events (such as
51+
proxy relationship details) will not be available in the response. To ensure complete event data is available,
52+
either pass ``wait_for_inclusion=True`` when calling this function, or retrieve the data manually from the
53+
blockchain using the extrinsic hash.
4554
"""
4655
try:
4756
if not (
@@ -273,23 +282,30 @@ async def create_pure_proxy_extrinsic(
273282
if response.success:
274283
logging.debug("[green]Pure proxy created successfully.[/green]")
275284

276-
# Extract pure proxy address from PureCreated triggered event
277-
for event in await response.extrinsic_receipt.triggered_events:
278-
if event.get("event_id") == "PureCreated":
279-
# Event structure: PureProxyCreated { disambiguation_index, proxy_type, pure, who }
280-
attributes = event.get("attributes", [])
281-
if attributes:
282-
response.data = {
283-
"pure_account": attributes.get("pure"),
284-
"spawner": attributes.get("who"),
285-
"proxy_type": attributes.get("proxy_type"),
286-
"index": attributes.get("disambiguation_index"),
287-
"height": await subtensor.substrate.get_block_number(
288-
response.extrinsic_receipt.block_hash
289-
),
290-
"ext_index": await response.extrinsic_receipt.extrinsic_idx,
291-
}
292-
break
285+
block_hash = (
286+
response.extrinsic_receipt.block_hash
287+
if response.extrinsic_receipt
288+
else None
289+
)
290+
291+
block_number, triggered_events, extrinsic_idx = (
292+
await asyncio.gather(
293+
subtensor.substrate.get_block_number(block_hash=block_hash),
294+
response.extrinsic_receipt.triggered_events,
295+
response.extrinsic_receipt.extrinsic_idx,
296+
)
297+
if (wait_for_finalization or wait_for_inclusion)
298+
and response.extrinsic_receipt.block_hash
299+
else (0, [], 0)
300+
)
301+
302+
response = apply_pure_proxy_data(
303+
response=response,
304+
triggered_events=triggered_events,
305+
block_number=block_number,
306+
extrinsic_idx=extrinsic_idx,
307+
raise_error=raise_error,
308+
)
293309
else:
294310
logging.error(f"[red]{response.message}[/red]")
295311

bittensor/core/extrinsics/proxy.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from bittensor.core.chain_data.proxy import ProxyType
44
from bittensor.core.extrinsics.pallets import Proxy
5+
from bittensor.core.extrinsics.utils import apply_pure_proxy_data
56
from bittensor.core.types import ExtrinsicResponse
67
from bittensor.utils.btlogging import logging
78

@@ -239,6 +240,13 @@ def create_pure_proxy_extrinsic(
239240
240241
Returns:
241242
ExtrinsicResponse: The result object of the extrinsic execution.
243+
244+
Warning:
245+
If ``wait_for_inclusion=False`` or when ``block_hash`` is not available, the extrinsic receipt may not contain
246+
triggered events. This means that any data that would normally be extracted from blockchain events (such as
247+
proxy relationship details) will not be available in the response. To ensure complete event data is available,
248+
either pass ``wait_for_inclusion=True`` when calling this function, or retrieve the data manually from the
249+
blockchain using the extrinsic hash.
242250
"""
243251
try:
244252
if not (
@@ -272,23 +280,28 @@ def create_pure_proxy_extrinsic(
272280
if response.success:
273281
logging.debug("[green]Pure proxy created successfully.[/green]")
274282

275-
# Extract pure proxy address from PureCreated triggered event
276-
for event in response.extrinsic_receipt.triggered_events:
277-
if event.get("event_id") == "PureCreated":
278-
# Event structure: PureProxyCreated { disambiguation_index, proxy_type, pure, who }
279-
attributes = event.get("attributes", [])
280-
if attributes:
281-
response.data = {
282-
"pure_account": attributes.get("pure"),
283-
"spawner": attributes.get("who"),
284-
"proxy_type": attributes.get("proxy_type"),
285-
"index": attributes.get("disambiguation_index"),
286-
"height": subtensor.substrate.get_block_number(
287-
response.extrinsic_receipt.block_hash
288-
),
289-
"ext_index": response.extrinsic_receipt.extrinsic_idx,
290-
}
291-
break
283+
block_number, triggered_events, extrinsic_idx = (
284+
(
285+
subtensor.substrate.get_block_number(
286+
block_hash=response.extrinsic_receipt.block_hash
287+
if response.extrinsic_receipt
288+
else None
289+
),
290+
response.extrinsic_receipt.triggered_events,
291+
response.extrinsic_receipt.extrinsic_idx,
292+
)
293+
if (wait_for_finalization or wait_for_inclusion)
294+
and response.extrinsic_receipt.block_hash
295+
else (0, [], 0)
296+
)
297+
298+
response = apply_pure_proxy_data(
299+
response=response,
300+
triggered_events=triggered_events,
301+
block_number=block_number,
302+
extrinsic_idx=extrinsic_idx,
303+
raise_error=raise_error,
304+
)
292305
else:
293306
logging.error(f"[red]{response.message}[/red]")
294307

bittensor/core/extrinsics/utils.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,65 @@ def sudo_call_extrinsic(
145145

146146
except Exception as error:
147147
return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error)
148+
149+
150+
def apply_pure_proxy_data(
151+
response: ExtrinsicResponse,
152+
triggered_events: list,
153+
block_number: int,
154+
extrinsic_idx: int,
155+
raise_error: bool,
156+
) -> ExtrinsicResponse:
157+
"""Apply pure proxy data to the response object.
158+
159+
Parameters:
160+
response: The response object to update.
161+
triggered_events: The triggered events of the transaction.
162+
block_number: The block number of the transaction.
163+
extrinsic_idx: The index of the extrinsic in the transaction.
164+
raise_error: Whether to raise an error if the data cannot be applied successfully.
165+
166+
Returns:
167+
True if the data was applied successfully, False otherwise.
168+
"""
169+
# Extract pure proxy address from PureCreated triggered event if wait_for_inclusion is True.
170+
for event in triggered_events:
171+
if event.get("event_id") == "PureCreated":
172+
# Event structure: PureProxyCreated { disambiguation_index, proxy_type, pure, who }
173+
attributes = event.get("attributes", [])
174+
if attributes:
175+
response.data = {
176+
"pure_account": attributes.get("pure"),
177+
"spawner": attributes.get("who"),
178+
"proxy_type": attributes.get("proxy_type"),
179+
"index": attributes.get("disambiguation_index"),
180+
"height": block_number,
181+
"ext_index": extrinsic_idx,
182+
}
183+
return response
184+
185+
# If triggered events are not available or event PureCreated does not exist in the response, return the response
186+
# with warning message ot raise the error if raise_error is True.
187+
message = (
188+
f"The ExtrinsicResponse doesn't contain pure_proxy data (`pure_account`, `spawner`, `proxy_type`, etc.) "
189+
f"because the extrinsic receipt doesn't have triggered events. This typically happens when "
190+
f"`wait_for_inclusion=False` or when `block_hash` is not available. To get this data, either pass "
191+
f"`wait_for_inclusion=True` when calling this function, or retrieve the data manually from the blockchain "
192+
f"using the extrinsic hash."
193+
)
194+
if response.extrinsic is not None and hasattr(response.extrinsic, "extrinsic_hash"):
195+
extrinsic_hash = response.extrinsic.extrinsic_hash
196+
hash_str = (
197+
extrinsic_hash.hex()
198+
if hasattr(extrinsic_hash, "hex")
199+
else str(extrinsic_hash)
200+
)
201+
message += f" Extrinsic hash: `{hash_str}`"
202+
203+
response.success = False
204+
response.message = message
205+
206+
if raise_error:
207+
raise RuntimeError(message)
208+
209+
return response.with_log("warning")

bittensor/core/subtensor.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3176,7 +3176,7 @@ def get_stake_info_for_coldkey(
31763176
block: The block number at which to query the stake information.
31773177
31783178
Returns:
3179-
An optional list of StakeInfo objects, or ``None`` if no stake information is found.
3179+
List of StakeInfo objects.
31803180
"""
31813181
result = self.query_runtime_api(
31823182
runtime_api="StakeInfoRuntimeApi",
@@ -3187,8 +3187,35 @@ def get_stake_info_for_coldkey(
31873187

31883188
if result is None:
31893189
return []
3190-
stakes: list[StakeInfo] = StakeInfo.list_from_dicts(result)
3191-
return [stake for stake in stakes if stake.stake > 0]
3190+
return StakeInfo.list_from_dicts(result)
3191+
3192+
def get_stake_info_for_coldkeys(
3193+
self, coldkey_ss58s: list[str], block: Optional[int] = None
3194+
) -> dict[str, list["StakeInfo"]]:
3195+
"""
3196+
Retrieves the stake information for multiple coldkeys.
3197+
3198+
Parameters:
3199+
coldkey_ss58s: A list of SS58 addresses of the coldkeys to query.
3200+
block: The block number at which to query the stake information.
3201+
3202+
Returns:
3203+
The dictionary mapping coldkey addresses to a list of StakeInfo objects.
3204+
"""
3205+
query = self.query_runtime_api(
3206+
runtime_api="StakeInfoRuntimeApi",
3207+
method="get_stake_info_for_coldkeys",
3208+
params=[coldkey_ss58s],
3209+
block=block,
3210+
)
3211+
3212+
if query is None:
3213+
return {}
3214+
3215+
return {
3216+
decode_account_id(ck): StakeInfo.list_from_dicts(st_info)
3217+
for ck, st_info in query
3218+
}
31923219

31933220
def get_stake_for_hotkey(
31943221
self, hotkey_ss58: str, netuid: int, block: Optional[int] = None
@@ -4530,7 +4557,7 @@ def sign_and_send_extrinsic(
45304557
wait_for_inclusion=wait_for_inclusion,
45314558
wait_for_finalization=wait_for_finalization,
45324559
)
4533-
extrinsic_response.extrinsic_receipt = response
4560+
45344561
# We only wait here if we expect finalization.
45354562
if not wait_for_finalization and not wait_for_inclusion:
45364563
extrinsic_response.extrinsic_fee = self.get_extrinsic_fee(
@@ -4542,6 +4569,8 @@ def sign_and_send_extrinsic(
45424569
logging.debug(extrinsic_response.message)
45434570
return extrinsic_response
45444571

4572+
extrinsic_response.extrinsic_receipt = response
4573+
45454574
if response.is_success:
45464575
extrinsic_response.extrinsic_fee = Balance.from_rao(
45474576
response.total_fee_amount

bittensor/core/types.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,7 @@ class ExtrinsicResponse:
352352
extrinsic_function: Optional[str] = None
353353
extrinsic: Optional["GenericExtrinsic"] = None
354354
extrinsic_fee: Optional["Balance"] = None
355-
extrinsic_receipt: Optional[Union["ExtrinsicReceipt", "AsyncExtrinsicReceipt"]] = (
356-
None
357-
)
355+
extrinsic_receipt: Optional["AsyncExtrinsicReceipt | ExtrinsicReceipt"] = None
358356
transaction_tao_fee: Optional["Balance"] = None
359357
transaction_alpha_fee: Optional["Balance"] = None
360358
error: Optional[Exception] = None

bittensor/extras/subtensor_api/staking.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]):
2424
subtensor.get_stake_for_coldkey_and_hotkey
2525
)
2626
self.get_stake_info_for_coldkey = subtensor.get_stake_info_for_coldkey
27+
self.get_stake_info_for_coldkeys = subtensor.get_stake_info_for_coldkeys
2728
self.get_stake_movement_fee = subtensor.get_stake_movement_fee
2829
self.get_stake_weight = subtensor.get_stake_weight
2930
self.get_unstake_fee = subtensor.get_unstake_fee

0 commit comments

Comments
 (0)