diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 08112f411c..e5cdda25c9 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -831,29 +831,18 @@ async def neurons( Understanding the distribution and status of neurons within a subnet is key to comprehending the network's decentralized structure and the dynamics of its consensus and governance processes. """ - if not block_hash: - if reuse_block: - block_hash = self.substrate.last_block_hash - else: - block_hash = await self.substrate.get_chain_head() - - neurons_lite, weights, bonds = await asyncio.gather( - self.neurons_lite(netuid=netuid, block_hash=block_hash), - self.weights(netuid=netuid, block_hash=block_hash), - self.bonds(netuid=netuid, block_hash=block_hash), + hex_bytes_result = await self.query_runtime_api( + runtime_api="NeuronInfoRuntimeApi", + method="get_neurons", + params=[netuid], + block_hash=block_hash, + reuse_block=reuse_block, ) - weights_as_dict = {uid: w for uid, w in weights} - bonds_as_dict = {uid: b for uid, b in bonds} - - neurons = [ - NeuronInfo.from_weights_bonds_and_neuron_lite( - neuron_lite, weights_as_dict, bonds_as_dict - ) - for neuron_lite in neurons_lite - ] + if hex_bytes_result is None: + return [] - return neurons + return NeuronInfo.list_from_vec_u8(hex_to_bytes(hex_bytes_result)) async def neurons_lite( self, netuid: int, block_hash: Optional[str] = None, reuse_block: bool = False diff --git a/bittensor/core/chain_data/neuron_info.py b/bittensor/core/chain_data/neuron_info.py index 478cdfa4c9..ecc1b2488c 100644 --- a/bittensor/core/chain_data/neuron_info.py +++ b/bittensor/core/chain_data/neuron_info.py @@ -174,3 +174,57 @@ def from_vec_u8(cls, vec_u8: bytes) -> "NeuronInfo": ), is_null=False, ) + + @classmethod + def list_from_vec_u8(cls, vec_u8: bytes) -> list["NeuronInfo"]: + nn = bt_decode.NeuronInfo.decode_vec(bytes(vec_u8)) + + def fix(n): + stake_dict = process_stake_data(n.stake) + total_stake = sum(stake_dict.values()) if stake_dict else Balance(0) + axon_info = n.axon_info + coldkey = decode_account_id(n.coldkey) + hotkey = decode_account_id(n.hotkey) + return NeuronInfo( + hotkey=hotkey, + coldkey=coldkey, + uid=n.uid, + netuid=n.netuid, + active=n.active, + stake=total_stake, + stake_dict=stake_dict, + total_stake=total_stake, + rank=u16_normalized_float(n.rank), + emission=n.emission / 1e9, + incentive=u16_normalized_float(n.incentive), + consensus=u16_normalized_float(n.consensus), + trust=u16_normalized_float(n.trust), + validator_trust=u16_normalized_float(n.validator_trust), + dividends=u16_normalized_float(n.dividends), + last_update=n.last_update, + validator_permit=n.validator_permit, + weights=[[e[0], e[1]] for e in n.weights], + bonds=[[e[0], e[1]] for e in n.bonds], + pruning_score=n.pruning_score, + prometheus_info=PrometheusInfo( + block=n.prometheus_info.block, + version=n.prometheus_info.version, + ip=str(netaddr.IPAddress(n.prometheus_info.ip)), + port=n.prometheus_info.port, + ip_type=n.prometheus_info.ip_type, + ), + axon_info=AxonInfo( + version=axon_info.version, + ip=str(netaddr.IPAddress(axon_info.ip)), + port=axon_info.port, + ip_type=axon_info.ip_type, + placeholder1=axon_info.placeholder1, + placeholder2=axon_info.placeholder2, + protocol=axon_info.protocol, + hotkey=hotkey, + coldkey=coldkey, + ), + is_null=False, + ) + + return [fix(n) for n in nn] diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index c09a1499c1..88595421b6 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -992,41 +992,32 @@ async def test_neurons(subtensor, mocker): # Preps fake_netuid = 1 fake_block_hash = "block_hash" - fake_neurons = [mocker.Mock(), mocker.Mock()] - fake_weights = [(1, [(10, 20), (30, 40)]), (2, [(50, 60), (70, 80)])] - fake_bonds = [(1, [(10, 20), (30, 40)]), (2, [(50, 60), (70, 80)])] - - mocked_neurons_lite = mocker.AsyncMock(return_value=fake_neurons) - subtensor.neurons_lite = mocked_neurons_lite - - mocked_weights = mocker.AsyncMock(return_value=fake_weights) - subtensor.weights = mocked_weights - - mocked_bonds = mocker.AsyncMock(return_value=fake_bonds) - subtensor.bonds = mocked_bonds + fake_reuse_block_hash = True - mocked_neuron_info_method = mocker.Mock() - async_subtensor.NeuronInfo.from_weights_bonds_and_neuron_lite = ( - mocked_neuron_info_method + mocked_query_runtime_api = mocker.patch.object( + subtensor, "query_runtime_api", return_value="NOT NONE" + ) + mocked_hex_to_bytes = mocker.patch.object(async_subtensor, "hex_to_bytes") + mocked_neuron_info_list_from_vec_u8 = mocker.patch.object( + async_subtensor.NeuronInfo, "list_from_vec_u8" ) - # Call - result = await subtensor.neurons(netuid=fake_netuid, block_hash=fake_block_hash) + result = await subtensor.neurons( + netuid=fake_netuid, + block_hash=fake_block_hash, + reuse_block=fake_reuse_block_hash, + ) # Asserts - mocked_neurons_lite.assert_awaited_once() - mocked_neurons_lite.assert_called_once_with( - netuid=fake_netuid, block_hash=fake_block_hash - ) - mocked_weights.assert_awaited_once() - mocked_weights.assert_called_once_with( - netuid=fake_netuid, block_hash=fake_block_hash + mocked_query_runtime_api.assert_called_once_with( + runtime_api="NeuronInfoRuntimeApi", + method="get_neurons", + params=[fake_netuid], + block_hash=fake_block_hash, + reuse_block=fake_reuse_block_hash, ) - mocked_bonds.assert_awaited_once() - mocked_bonds.assert_called_once_with(netuid=fake_netuid, block_hash=fake_block_hash) - assert result == [ - mocked_neuron_info_method.return_value for _ in range(len(fake_neurons)) - ] + mocked_hex_to_bytes.assert_called_once_with(mocked_query_runtime_api.return_value) + assert result == mocked_neuron_info_list_from_vec_u8.return_value @pytest.mark.parametrize(