Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions bittensor/core/async_subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4803,24 +4803,45 @@ async def get_subnet_prices(
Notes:
Subnet 0 (root network) always has a price of 1 TAO since it uses TAO directly rather than Alpha.
"""
# TODO: we will maintain this logic until we receive a function that returns all subnet prices in the chain as a
# single call.
api = "SwapRuntimeApi"
method = "current_alpha_price_all"
block_hash = await self.determine_block_hash(block, block_hash)
prices = {0: Balance.from_tao(1)}
netuids = await self.get_all_subnets_netuid(
block=block, block_hash=block_hash, reuse_block=reuse_block
)
try:
prices_rao = (
await self.substrate.runtime_call(
api=api,
method=method,
block_hash=block_hash,
)
).value
return {p["netuid"]: Balance.from_rao(p["price"]) for p in prices_rao}

except ValueError as err:
msg = str(err)
if f"{api}.{method}" in msg and "not found in registry" in msg:
logging.warning(
f"Runtime ([blue]{self})[/blue] at block=[blue]{block or self.block}[/blue] is missing `[red]{api}."
f"{method}[/red]` runtime API. Using deprecated subnet price retrieval method as a fallback."
)
prices = {0: Balance.from_tao(1)}
netuids = await self.get_all_subnets_netuid(
block=block, block_hash=block_hash, reuse_block=reuse_block
)

tasks = [
self.get_subnet_price(
netuid, block=block, block_hash=block_hash, reuse_block=reuse_block
)
for netuid in netuids
]
tasks = [
self.get_subnet_price(
netuid,
block=block,
block_hash=block_hash,
reuse_block=reuse_block,
)
for netuid in netuids
]

prices_list = await asyncio.gather(*tasks)
prices.update(dict(zip(netuids, prices_list)))
return prices
prices_list = await asyncio.gather(*tasks)
prices.update(dict(zip(netuids, prices_list)))
return prices
raise

async def get_subnet_reveal_period_epochs(
self, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None
Expand Down
36 changes: 28 additions & 8 deletions bittensor/core/subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3940,14 +3940,34 @@ def get_subnet_prices(
Notes:
Subnet 0 (root network) always has a price of 1 TAO since it uses TAO directly rather than Alpha.
"""
# TODO: we will maintain this logic until we receive a function that returns all subnet prices in the chain as a
# single call.
prices = {0: Balance.from_tao(1)}
netuids = self.get_all_subnets_netuid(block=block)
prices.update(
{netuid: self.get_subnet_price(netuid, block=block) for netuid in netuids}
)
return prices
api = "SwapRuntimeApi"
method = "current_alpha_price_all"
try:
block_hash = self.determine_block_hash(block=block)
prices_rao = self.substrate.runtime_call(
api=api,
method=method,
block_hash=block_hash,
).value
return {p["netuid"]: Balance.from_rao(p["price"]) for p in prices_rao}

except ValueError as err:
msg = str(err)
if f"{api}.{method}" in msg and "not found in registry" in msg:
logging.warning(
f"Runtime ([blue]{self})[/blue] at block=[blue]{block or self.block}[/blue] is missing `[red]{api}."
f"{method}[/red]` runtime API. Using deprecated subnet price retrieval method as a fallback."
)
prices = {0: Balance.from_tao(1)}
netuids = self.get_all_subnets_netuid(block=block)
prices.update(
{
netuid: self.get_subnet_price(netuid, block=block)
for netuid in netuids
}
)
return prices
raise

def get_subnet_reveal_period_epochs(
self, netuid: int, block: Optional[int] = None
Expand Down
66 changes: 60 additions & 6 deletions tests/unit_tests/test_async_subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3844,16 +3844,56 @@ async def test_get_subnet_price(subtensor, mocker):

@pytest.mark.asyncio
async def test_get_subnet_prices(subtensor, mocker):
"""Test get_subnet_prices returns the correct value."""
"""Test get_subnet_prices returns prices from runtime API."""
# Preps
fake_block_hash = "fake_block_hash"
fake_prices_rao = [
{"netuid": 0, "price": 1_000_000_000},
{"netuid": 1, "price": 29258617},
]
mocked_determine_block_hash = mocker.patch.object(
subtensor, "determine_block_hash", return_value=fake_block_hash
)
mocker.patch.object(
subtensor.substrate,
"runtime_call",
return_value=mocker.Mock(value=fake_prices_rao),
)

# Call
result = await subtensor.get_subnet_prices()

# Asserts
mocked_determine_block_hash.assert_awaited_once_with(None, None)
subtensor.substrate.runtime_call.assert_awaited_once_with(
api="SwapRuntimeApi",
method="current_alpha_price_all",
block_hash=fake_block_hash,
)
assert result == {
0: Balance.from_rao(1_000_000_000),
1: Balance.from_rao(29258617),
}


@pytest.mark.asyncio
async def test_get_subnet_prices_fallback(subtensor, mocker):
"""Test get_subnet_prices falls back to per-subnet price retrieval when runtime API is missing."""
# Preps
fake_block_hash = "fake_block_hash"
fake_netuids = [1, 2]
fake_price_1 = Balance.from_tao(0.5)
fake_price_2 = Balance.from_tao(1.5)
fake_block_hash = "fake_block_hash"

mocked_determine_block_hash = mocker.patch.object(
subtensor, "determine_block_hash", return_value=fake_block_hash
)
mocker.patch.object(
subtensor.substrate,
"runtime_call",
side_effect=ValueError(
"SwapRuntimeApi.current_alpha_price_all not found in registry"
),
)
mocked_get_all_subnets_netuid = mocker.patch.object(
subtensor, "get_all_subnets_netuid", return_value=fake_netuids
)
Expand All @@ -3868,8 +3908,6 @@ async def fake_get_subnet_price(netuid, **kwargs):
subtensor, "get_subnet_price", side_effect=fake_get_subnet_price
)

expected_prices = {0: Balance.from_tao(1), 1: fake_price_1, 2: fake_price_2}

# Call
result = await subtensor.get_subnet_prices()

Expand All @@ -3879,7 +3917,23 @@ async def fake_get_subnet_price(netuid, **kwargs):
block=None, block_hash=fake_block_hash, reuse_block=False
)
assert mocked_get_subnet_price.call_count == 2
assert result == expected_prices
assert result == {0: Balance.from_tao(1), 1: fake_price_1, 2: fake_price_2}


@pytest.mark.asyncio
async def test_get_subnet_prices_raises_unrelated_value_error(subtensor, mocker):
"""Test get_subnet_prices re-raises ValueError when it's not about missing runtime API."""
# Preps
mocker.patch.object(subtensor, "determine_block_hash", return_value="fake_hash")
mocker.patch.object(
subtensor.substrate,
"runtime_call",
side_effect=ValueError("something else went wrong"),
)

# Call & Assert
with pytest.raises(ValueError, match="something else went wrong"):
await subtensor.get_subnet_prices()


@pytest.mark.asyncio
Expand Down
63 changes: 58 additions & 5 deletions tests/unit_tests/test_subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4125,21 +4125,59 @@ def test_get_subnet_price(subtensor, mocker):


def test_get_subnet_prices(subtensor, mocker):
"""Test get_subnet_prices returns the correct value."""
"""Test get_subnet_prices returns prices from runtime API."""
# Preps
fake_block_hash = "fake_block_hash"
fake_prices_rao = [
{"netuid": 0, "price": 1_000_000_000},
{"netuid": 1, "price": 29258617},
]
mocked_determine_block_hash = mocker.patch.object(
subtensor, "determine_block_hash", return_value=fake_block_hash
)
mocker.patch.object(
subtensor.substrate,
"runtime_call",
return_value=mocker.Mock(value=fake_prices_rao),
)

# Call
result = subtensor.get_subnet_prices()

# Asserts
mocked_determine_block_hash.assert_called_once_with(block=None)
subtensor.substrate.runtime_call.assert_called_once_with(
api="SwapRuntimeApi",
method="current_alpha_price_all",
block_hash=fake_block_hash,
)
assert result == {
0: Balance.from_rao(1_000_000_000),
1: Balance.from_rao(29258617),
}


def test_get_subnet_prices_fallback(subtensor, mocker):
"""Test get_subnet_prices falls back to per-subnet price retrieval when runtime API is missing."""
# Preps
fake_netuids = [1, 2]
fake_price_1 = Balance.from_tao(0.5)
fake_price_2 = Balance.from_tao(1.5)

mocker.patch.object(subtensor, "determine_block_hash")
mocker.patch.object(
subtensor.substrate,
"runtime_call",
side_effect=ValueError(
"SwapRuntimeApi.current_alpha_price_all not found in registry"
),
)
mocked_get_all_subnets_netuid = mocker.patch.object(
subtensor, "get_all_subnets_netuid", return_value=fake_netuids
)
mocked_get_subnet_price = mocker.patch.object(
subtensor, "get_subnet_price", side_effect=[fake_price_1, fake_price_2]
)

expected_prices = {0: Balance.from_tao(1), 1: fake_price_1, 2: fake_price_2}

# Call
result = subtensor.get_subnet_prices()

Expand All @@ -4148,7 +4186,22 @@ def test_get_subnet_prices(subtensor, mocker):
assert mocked_get_subnet_price.call_count == 2
mocked_get_subnet_price.assert_any_call(1, block=None)
mocked_get_subnet_price.assert_any_call(2, block=None)
assert result == expected_prices
assert result == {0: Balance.from_tao(1), 1: fake_price_1, 2: fake_price_2}


def test_get_subnet_prices_raises_unrelated_value_error(subtensor, mocker):
"""Test get_subnet_prices re-raises ValueError when it's not about missing runtime API."""
# Preps
mocker.patch.object(subtensor, "determine_block_hash")
mocker.patch.object(
subtensor.substrate,
"runtime_call",
side_effect=ValueError("something else went wrong"),
)

# Call & Assert
with pytest.raises(ValueError, match="something else went wrong"):
subtensor.get_subnet_prices()


def test_all_subnets(subtensor, mocker):
Expand Down
Loading