Vulnerability History
Date | High Risk | Low Risk |
---|---|---|
2025-04-29 | 1 | 1 |
Audit Report Details
4127
Lines of Code
0
Resolved
π¨ High Risk Vulnerabilities
None found
β οΈ Low Risk Vulnerabilities
None found
Vulnerable Code:
1# Repo Tree (Python files only, excluding .gitignored files)23βββ contrib4βββ docs5β βββ stream_tutorial6β βββ client.py7β βββ config.py8β βββ miner.py9β βββ protocol.py10βββ neurons11β βββ __init__.py12β βββ miner.py13β βββ validator.py14βββ node-stack15β βββ miner16β β βββ modules17β β βββ routes18β β βββ tests19β βββ validator20β βββ modules21β βββ routes22β βββ tests23βββ scripts24βββ setup.py25βββ sybil26β βββ __init__.py27β βββ api28β β βββ __init__.py29β β βββ dummy.py30β β βββ get_query_axons.py31β βββ base32β β βββ __init__.py33β β βββ miner.py34β β βββ neuron.py35β β βββ utils36β β β βββ __init__.py37β β β βββ weight_utils.py38β β βββ validator.py39β βββ mock.py40β βββ protocol.py41β βββ subnet_links.py42β βββ utils43β β βββ __init__.py44β β βββ config.py45β β βββ logging.py46β β βββ misc.py47β β βββ uids.py48β βββ validator49β βββ __init__.py50β βββ forward.py51β βββ reward.py52β βββ utils.py53βββ tests54β βββ __init__.py55β βββ helpers.py56β βββ test_mock.py57β βββ test_sybil_validator.py58βββ verify59 βββ generate.py60 βββ verify.py616263# Complete repo contents (files-to-prompt output)6465target_repo/setup.py66---67# The MIT License (MIT)68# Copyright Β© 2023 Yuma Rao69# TODO(developer): Set your name70# Copyright Β© 2023 <your name>7172# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated73# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation74# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,75# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:7677# The above copyright notice and this permission notice shall be included in all copies or substantial portions of78# the Software.7980# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO81# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL82# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION83# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER84# DEALINGS IN THE SOFTWARE.8586import re87import os88import codecs89import pathlib90from os import path91from io import open92from setuptools import setup, find_packages93from pkg_resources import parse_requirements949596def read_requirements(path):97 with open(path, "r") as f:98 requirements = f.read().splitlines()99 processed_requirements = []100101 for req in requirements:102 # For git or other VCS links103 if req.startswith("git+") or "@" in req:104 pkg_name = re.search(r"(#egg=)([\w\-_]+)", req)105 if pkg_name:106 processed_requirements.append(pkg_name.group(2))107 else:108 # You may decide to raise an exception here,109 # if you want to ensure every VCS link has an #egg=<package_name> at the end110 continue111 else:112 processed_requirements.append(req)113 return processed_requirements114115116requirements = read_requirements("requirements.txt")117here = path.abspath(path.dirname(__file__))118119with open(path.join(here, "README.md"), encoding="utf-8") as f:120 long_description = f.read()121122# loading version from setup.py123with codecs.open(124 os.path.join(here, "sybil/__init__.py"), encoding="utf-8"125) as init_file:126 version_match = re.search(127 r"^__version__ = ['\"]([^'\"]*)['\"]", init_file.read(), re.M128 )129 version_string = version_match.group(1)130131setup(132 name="Sybil Network", # TODO(developer): Change this value to your module subnet name.133 version=version_string,134 description="Bittensor Subnet for VPN Services", # TODO(developer): Change this value to your module subnet description.135 long_description=long_description,136 long_description_content_type="text/markdown",137 url="https://github.com/beyond-stake/sybil-network", # TODO(developer): Change this url to your module subnet github url.138 author="mentor.eth", # TODO(developer): Change this value to your module subnet author name.139 packages=find_packages(),140 include_package_data=True,141 author_email="", # TODO(developer): Change this value to your module subnet author email.142 license="MIT",143 python_requires=">=3.8",144 install_requires=requirements,145 classifiers=[146 "Development Status :: 3 - Alpha",147 "Intended Audience :: Developers",148 "Topic :: Software Development :: Build Tools",149 # Pick your license as you wish150 "License :: OSI Approved :: MIT License",151 "Programming Language :: Python :: 3 :: Only",152 "Programming Language :: Python :: 3.8",153 "Programming Language :: Python :: 3.9",154 "Programming Language :: Python :: 3.10",155 "Topic :: Scientific/Engineering",156 "Topic :: Scientific/Engineering :: Mathematics",157 "Topic :: Scientific/Engineering :: Artificial Intelligence",158 "Topic :: Software Development",159 "Topic :: Software Development :: Libraries",160 "Topic :: Software Development :: Libraries :: Python Modules",161 ],162)163164165---166target_repo/verify/generate.py167---168from datetime import datetime169170import bittensor171172# Hardcode or set the environment variable WALLET_PASS to the password for the wallet173# environ["WALLET_PASS"] = ""174175176def main(args):177 wallet = bittensor.wallet(name=args.name)178 keypair = wallet.coldkey179180 timestamp = datetime.now()181 timezone = timestamp.astimezone().tzname()182183 # ensure compatiblity with polkadotjs messages, as polkadotjs always wraps message184 message = (185 "<Bytes>" + f"On {timestamp} {timezone} {args.message}" + "</Bytes>"186 )187 signature = keypair.sign(data=message)188189 file_contents = f"{message}\n\tSigned by: {keypair.ss58_address}\n\tSignature: {signature.hex()}"190 print(file_contents)191 open("message_and_signature.txt", "w").write(file_contents)192193 print("Signature generated and saved to message_and_signature.txt")194195196if __name__ == "__main__":197 import argparse198199 parser = argparse.ArgumentParser(description="Generate a signature")200 parser.add_argument("--message", help="The message to sign", type=str)201 parser.add_argument("--name", help="The wallet name", type=str)202 args = parser.parse_args()203204 main(args)205206207---208target_repo/verify/verify.py209---210from binascii import unhexlify211212from substrateinterface import Keypair213214215def main(args):216 file_data = open(args.file).read()217 file_split = file_data.split("\n\t")218219 address_line = file_split[1]220 address_prefix = "Signed by: "221 if address_line.startswith(address_prefix):222 address = address_line[len(address_prefix) :]223 else:224 address = address_line225226 keypair = Keypair(ss58_address=address, ss58_format=42)227228 message = file_split[0]229 if not message.startswith("<Bytes>") or not message.endswith("</Bytes>"):230 raise ValueError("Message is not properly wrapped in <Bytes>.")231232 signature_line = file_split[2]233 signature_prefix = "Signature: "234 if signature_line.startswith(signature_prefix):235 signature = signature_line[len(signature_prefix) :]236 else:237 signature = signature_line238239 real_signature = unhexlify(signature.encode())240241 if not keypair.verify(data=message, signature=real_signature):242 raise ValueError(f"Invalid signature for address={address}")243 else:244 print(f"Signature verified, signed by {address}")245246247if __name__ == "__main__":248 import argparse249250 parser = argparse.ArgumentParser(description="Verify a signature")251 parser.add_argument(252 "--file", help="The file containing the message and signature"253 )254 args = parser.parse_args()255 main(args)256257258---259target_repo/sybil/__init__.py260---261# The MIT License (MIT)262# Copyright Β© 2023 Yuma Rao263# TODO(developer): Set your name264# Copyright Β© 2023 <your name>265266# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated267# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation268# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,269# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:270271# The above copyright notice and this permission notice shall be included in all copies or substantial portions of272# the Software.273274# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO275# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL276# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION277# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER278# DEALINGS IN THE SOFTWARE.279280# TODO(developer): Change this value when updating your code base.281# Define the version of the template module.282__version__ = "0.0.0"283version_split = __version__.split(".")284__spec_version__ = (285 (1000 * int(version_split[0]))286 + (10 * int(version_split[1]))287 + (1 * int(version_split[2]))288)289290# Import all submodules.291from . import protocol292from . import base293from . import validator294from . import api295from .subnet_links import SUBNET_LINKS296297298---299target_repo/sybil/mock.py300---301import time302303import asyncio304import random305import bittensor as bt306307from typing import List308309310class MockSubtensor(bt.MockSubtensor):311 def __init__(self, netuid, n=16, wallet=None, network="mock"):312 super().__init__(network=network)313314 if not self.subnet_exists(netuid):315 self.create_subnet(netuid)316317 # Register ourself (the validator) as a neuron at uid=0318 if wallet is not None:319 self.force_register_neuron(320 netuid=netuid,321 hotkey=wallet.hotkey.ss58_address,322 coldkey=wallet.coldkey.ss58_address,323 balance=100000,324 stake=100000,325 )326327 # Register n mock neurons who will be miners328 for i in range(1, n + 1):329 self.force_register_neuron(330 netuid=netuid,331 hotkey=f"miner-hotkey-{i}",332 coldkey="mock-coldkey",333 balance=100000,334 stake=100000,335 )336337338class MockMetagraph(bt.metagraph):339 def __init__(self, netuid=1, network="mock", subtensor=None):340 super().__init__(netuid=netuid, network=network, sync=False)341342 if subtensor is not None:343 self.subtensor = subtensor344 self.sync(subtensor=subtensor)345346 for axon in self.axons:347 axon.ip = "127.0.0.0"348 axon.port = 8091349350 bt.logging.info(f"Metagraph: {self}")351 bt.logging.info(f"Axons: {self.axons}")352353354class MockDendrite(bt.dendrite):355 """356 Replaces a real bittensor network request with a mock request that just returns some static response for all axons that are passed and adds some random delay.357 """358359 def __init__(self, wallet):360 super().__init__(wallet)361362 async def forward(363 self,364 axons: List[bt.axon],365 synapse: bt.Synapse = bt.Synapse(),366 timeout: float = 12,367 deserialize: bool = True,368 run_async: bool = True,369 streaming: bool = False,370 ):371 if streaming:372 raise NotImplementedError("Streaming not implemented yet.")373374 async def query_all_axons(streaming: bool):375 """Queries all axons for responses."""376377 async def single_axon_response(i, axon):378 """Queries a single axon for a response."""379380 start_time = time.time()381 s = synapse.copy()382 # Attach some more required data so it looks real383 s = self.preprocess_synapse_for_request(axon, s, timeout)384 # We just want to mock the response, so we'll just fill in some data385 process_time = random.random()386 if process_time < timeout:387 s.dendrite.process_time = str(time.time() - start_time)388 # Update the status code and status message of the dendrite to match the axon389 # TODO (developer): replace with your own expected synapse data390 s.dummy_output = s.dummy_input * 2391 s.dendrite.status_code = 200392 s.dendrite.status_message = "OK"393 synapse.dendrite.process_time = str(process_time)394 else:395 s.dummy_output = 0396 s.dendrite.status_code = 408397 s.dendrite.status_message = "Timeout"398 synapse.dendrite.process_time = str(timeout)399400 # Return the updated synapse object after deserializing if requested401 if deserialize:402 return s.deserialize()403 else:404 return s405406 return await asyncio.gather(407 *(408 single_axon_response(i, target_axon)409 for i, target_axon in enumerate(axons)410 )411 )412413 return await query_all_axons(streaming)414415 def __str__(self) -> str:416 """417 Returns a string representation of the Dendrite object.418419 Returns:420 str: The string representation of the Dendrite object in the format "dendrite(<user_wallet_address>)".421 """422 return "MockDendrite({})".format(self.keypair.ss58_address)423424425---426target_repo/sybil/protocol.py427---428# The MIT License (MIT)429# Copyright Β© 2023 Yuma Rao430# TODO(developer): Set your name431# Copyright Β© 2023 <your name>432433# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated434# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation435# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,436# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:437438# The above copyright notice and this permission notice shall be included in all copies or substantial portions of439# the Software.440441# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO442# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL443# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION444# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER445# DEALINGS IN THE SOFTWARE.446447import typing448import bittensor as bt449450# TODO(developer): Rewrite with your protocol definition.451452# This is the protocol for the dummy miner and validator.453# It is a simple request-response protocol where the validator sends a request454# to the miner, and the miner responds with a dummy response.455456# ---- miner ----457# Example usage:458# def dummy( synapse: Dummy ) -> Dummy:459# synapse.dummy_output = synapse.dummy_input + 1460# return synapse461# axon = bt.axon().attach( dummy ).serve(netuid=...).start()462463# ---- validator ---464# Example usage:465# dendrite = bt.dendrite()466# dummy_output = dendrite.query( Dummy( dummy_input = 1 ) )467# assert dummy_output == 2468469470class Dummy(bt.Synapse):471 """472 A simple dummy protocol representation which uses bt.Synapse as its base.473 This protocol helps in handling dummy request and response communication between474 the miner and the validator.475476 Attributes:477 - dummy_input: An integer value representing the input request sent by the validator.478 - dummy_output: An optional integer value which, when filled, represents the response from the miner.479 """480481 # Required request input, filled by sending dendrite caller.482 dummy_input: int483484 # Optional request output, filled by receiving axon.485 dummy_output: typing.Optional[int] = None486487 def deserialize(self) -> int:488 """489 Deserialize the dummy output. This method retrieves the response from490 the miner in the form of dummy_output, deserializes it and returns it491 as the output of the dendrite.query() call.492493 Returns:494 - int: The deserialized response, which in this case is the value of dummy_output.495496 Example:497 Assuming a Dummy instance has a dummy_output value of 5:498 >>> dummy_instance = Dummy(dummy_input=4)499 >>> dummy_instance.dummy_output = 5500 >>> dummy_instance.deserialize()501 5502 """503 return self.dummy_output504505506class Challenge(bt.Synapse):507 """508 A challenge protocol representation which uses bt.Synapse as its base.509 This protocol helps in handling challenge request and response communication between510 the miner and the validator.511 """512 513 challenge: str514 challenge_url: str515 challenge_response: typing.Optional[str]=None516517 def deserialize(self) -> str:518 """519 Deserialize the challenge response. This method retrieves the response from520 the miner in the form of challenge_response, deserializes it and returns it521 as the output of the dendrite.query() call.522 """523 524 return self.challenge_response525526527---528target_repo/sybil/subnet_links.py529---530SUBNET_LINKS = [531 {"name": "sn0", "url": ""},532 {"name": "sn1", "url": "https://github.com/opentensor/prompting/"},533 {534 "name": "sn2",535 "url": "https://github.com/inference-labs-inc/omron-subnet/",536 },537 {538 "name": "sn3",539 "url": "https://github.com/myshell-ai/MyShell-TTS-Subnet/",540 },541 {"name": "sn4", "url": "https://github.com/manifold-inc/targon/"},542 {"name": "sn5", "url": "https://github.com/OpenKaito/openkaito/"},543 {544 "name": "sn6",545 "url": "https://github.com/amedeo-gigaver/infinite_games/",546 },547 {"name": "sn7", "url": "https://github.com/eclipsevortex/SubVortex/"},548 {549 "name": "sn8",550 "url": "https://github.com/taoshidev/proprietary-trading-network/",551 },552 {"name": "sn9", "url": "https://github.com/unconst/pretrain-subnet/"},553 {554 "name": "sn10",555 "url": "https://github.com/Sturdy-Subnet/sturdy-subnet/",556 },557 {558 "name": "sn11",559 "url": "https://github.com/impel-intelligence/dippy-bittensor-subnet/",560 },561 {562 "name": "sn12",563 "url": "https://github.com/backend-developers-ltd/ComputeHorde/",564 },565 {"name": "sn13", "url": "https://github.com/macrocosm-os/data-universe/"},566 {567 "name": "sn14",568 "url": "https://github.com/synapsec-ai/llm-defender-subnet/",569 },570 {571 "name": "sn15",572 "url": "https://github.com/blockchain-insights/blockchain-data-subnet/",573 },574 {"name": "sn16", "url": "https://github.com/eseckft/BitAds.ai/"},575 {"name": "sn17", "url": "https://github.com/404-Repo/three-gen-subnet/"},576 {"name": "sn18", "url": "https://github.com/corcel-api/cortex.t/"},577 {"name": "sn19", "url": "https://github.com/namoray/vision/"},578 {"name": "sn20", "url": "https://github.com/RogueTensor/bitagent_subnet/"},579 {580 "name": "sn21",581 "url": "https://github.com/omegalabsinc/omegalabs-anytoany-bittensor",582 },583 {"name": "sn22", "url": "https://github.com/Datura-ai/smart-scrape/"},584 {585 "name": "sn23",586 "url": "https://github.com/SocialTensor/SocialTensorSubnet/",587 },588 {589 "name": "sn24",590 "url": "https://github.com/omegalabsinc/omegalabs-bittensor-subnet/",591 },592 {"name": "sn25", "url": "https://github.com/macrocosm-os/folding/"},593 {594 "name": "sn26",595 "url": "https://github.com/TensorAlchemy/TensorAlchemy/",596 },597 {598 "name": "sn27",599 "url": "https://github.com/neuralinternet/compute-subnet/",600 },601 {"name": "sn28", "url": "https://github.com/foundryservices/snpOracle/"},602 {"name": "sn29", "url": "https://github.com/fractal-net/fractal/"},603 {"name": "sn30", "url": "https://github.com/Bettensor/bettensor/"},604 {605 "name": "sn31",606 "url": "https://github.com/nimaaghli/NASChain/",607 },608 {"name": "sn32", "url": "https://github.com/It-s-AI/llm-detection/"},609 {610 "name": "sn33",611 "url": "https://github.com/afterpartyai/bittensor-conversation-genome-project/",612 },613 {"name": "sn34", "url": "https://github.com/Healthi-Labs/healthi-subnet/"},614 {615 "name": "sn35",616 "url": "https://github.com/LogicNet-Subnet/LogicNet-prod/",617 },618 {"name": "sn36", "url": "https://github.com/HIP-Labs/HIP-Subnet/"},619 {"name": "sn37", "url": "https://github.com/macrocosm-os/finetuning/"},620]621622623---624target_repo/sybil/validator/__init__.py625---626from .forward import forward627from .reward import reward628629630---631target_repo/sybil/validator/forward.py632---633# The MIT License (MIT)634# Copyright Β© 2023 Yuma Rao635# TODO(developer): Set your name636# Copyright Β© 2023 <your name>637638# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated639# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation640# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,641# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:642643# The above copyright notice and this permission notice shall be included in all copies or substantial portions of644# the Software.645646# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO647# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL648# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION649# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER650# DEALINGS IN THE SOFTWARE.651652import time653import bittensor as bt654import asyncio655import aiohttp656657from sybil.protocol import Challenge658from sybil.validator.utils import generate_challenges659from sybil.utils.uids import get_random_uids660from sybil.validator.reward import get_rewards661async def forward(self):662 """663 The forward function is called by the validator every time step.664665 It is responsible for querying the network and scoring the responses.666667 Args:668 self (:obj:`bittensor.neuron.Neuron`): The neuron object which contains all the necessary state for the validator.669670 """671 672 # Post miner and validator info to the container 673 miners_info = []674 validators_info = []675 for uid in range(self.metagraph.n.item()):676 if self.metagraph.axons[uid].is_serving:677 miners_info.append({678 "uid": uid,679 "ip": self.metagraph.axons[uid].ip,680 })681 elif self.metagraph.validator_permit[uid]:682 validators_info.append({683 "uid": uid,684 "ip": self.metagraph.axons[uid].ip,685 "stake": self.metagraph.S[uid],686 })687 try:688 async with aiohttp.ClientSession() as session:689 async with session.post(690 f"{self.validator_server_url}/protocol/broadcast/miners",691 json={"miners": miners_info}692 ) as resp:693 result = await resp.json()694 if result["success"]:695 bt.logging.info(f"Broadcasted miners info: {len(miners_info)} miners")696 else:697 bt.logging.error(f"Failed to broadcast miners info")698 699 async with aiohttp.ClientSession() as session:700 async with session.post(701 f"{self.validator_server_url}/protocol/broadcast/validators",702 json={"validators": validators_info}703 ) as resp:704 result = await resp.json()705 if result["success"]:706 bt.logging.info(f"Broadcasted validators info: {len(validators_info)} validators")707 else:708 bt.logging.error(f"Failed to broadcast validators info")709 except Exception as e:710 bt.logging.error(f"Failed to broadcast miners or validators info: {e}")711 712 # get_random_uids is an example method, but you can replace it with your own.713 miner_uids = get_random_uids(self, k=self.config.neuron.sample_size)714 bt.logging.info(f"Miner uids: {miner_uids}")715 716 # Generate k challenges717 challenges = await generate_challenges(miner_uids=miner_uids, validator_server_url=self.validator_server_url)718 bt.logging.info(f"Generated challenges:\n" + "\n".join([str(challenge) for challenge in challenges]))719 720 if challenges is None:721 bt.logging.error("Failed to generate challenges")722 time.sleep(10)723 return724725 # Create concurrent queries, one for each challenge-miner pair726 async_queries = [727 self.dendrite(728 axons=[self.metagraph.axons[uid]],729 synapse=challenge,730 deserialize=True,731 )732 for uid, challenge in zip(miner_uids, challenges)733 ]734735 # Execute all queries concurrently736 responses = await asyncio.gather(*async_queries)737738 bt.logging.info(f"Received Raw responses: {responses}")739 # Flatten the responses list since each query returns a list with one item740 responses = [resp[0] for resp in responses]741742 # Log the results for monitoring purposes.743 bt.logging.info(f"Received responses: {responses}")744 745 # Get scores for the responses746 rewards = await get_rewards([challenge.challenge for challenge in challenges], responses, validator_server_url=self.validator_server_url)747 bt.logging.info(f"Scores: {rewards}")748749 if rewards is None:750 bt.logging.error("Failed to get rewards")751 time.sleep(10)752 return753754 # Update the scores based on the rewards. You may want to define your own update_scores function for custom behavior.755 self.update_scores(rewards, miner_uids)756757 time.sleep(10)758759760---761target_repo/sybil/validator/reward.py762---763# The MIT License (MIT)764# Copyright Β© 2023 Yuma Rao765# TODO(developer): Set your name766# Copyright Β© 2023 <your name>767768# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated769# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation770# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,771# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:772773# The above copyright notice and this permission notice shall be included in all copies or substantial portions of774# the Software.775776# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO777# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL778# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION779# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER780# DEALINGS IN THE SOFTWARE.781import numpy as np782from typing import List783import bittensor as bt784import aiohttp785import asyncio786787def reward(query: int, response: int) -> float:788 """789 Reward the miner response to the dummy request. This method returns a reward790 value for the miner, which is used to update the miner's score.791792 Returns:793 - float: The reward value for the miner.794 """795 bt.logging.info(796 f"In rewards, query val: {query}, response val: {response}, rewards val: {1.0 if response == query * 2 else 0}"797 )798 return 1.0 if response == query * 2 else 0799800801async def get_rewards(challenges: List[str], responses: List[str], validator_server_url: str) -> List[float]:802 try:803 """804 Get the scores for the responses.805 """806 async def fetch_score(challenge, response) -> float:807 bt.logging.info(f"Getting score at: {validator_server_url}/challenge/{challenge}/{response}")808 if response is None:809 return 0810 async with aiohttp.ClientSession() as session:811 async with session.get(812 f"{validator_server_url}/challenge/{challenge}/{response}"813 ) as resp:814 result = await resp.json()815 if result["score"]:816 bt.logging.info(f"Score: {result['score']}")817 else:818 bt.logging.info(f"No score found in response: {result}")819 return result["score"] if "score" in result else 0820 821 # Concurrently fetch all scores822 scores = await asyncio.gather(823 *[fetch_score(challenge, response) for challenge, response in zip(challenges, responses)]824 )825 826 # Convert None to 0827 scores = [0 if score is None else score for score in scores]828 829 return scores830 except Exception as e:831 print(f"Error getting rewards: {e}")832 return None833834835---836target_repo/sybil/validator/utils.py837---838import asyncio839import aiohttp840from sybil.protocol import Challenge841from typing import List842import bittensor as bt843844845# Fetch a challenge from a given URL846async def fetch(url):847 async with aiohttp.ClientSession() as session:848 async with session.get(url) as response:849 return await response.json()850851# Generate one challenge per miner_uid, appending ?miner_uid=<uid> to each request852async def generate_challenges(miner_uids: List[int], validator_server_url: str) -> List[Challenge]:853 try:854 tasks = []855 for uid in miner_uids:856 bt.logging.info(f"Generating challenge for miner uid: {uid}")857 url = f"{validator_server_url}/challenge/new?miner_uid={uid}"858 tasks.append(fetch(url))859 860 responses = await asyncio.gather(*tasks)861 862 challenges = [863 Challenge(864 challenge=response["challenge"],865 challenge_url=response["challenge_url"]866 ) for response in responses867 ]868 869 return challenges870 except Exception as e:871 print(f"Error generating challenges: {e}")872 return None873874875---876target_repo/sybil/utils/__init__.py877---878from . import config879from . import misc880from . import uids881882883---884target_repo/sybil/utils/config.py885---886# The MIT License (MIT)887# Copyright Β© 2023 Yuma Rao888# Copyright Β© 2023 Opentensor Foundation889890# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated891# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation892# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,893# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:894895# The above copyright notice and this permission notice shall be included in all copies or substantial portions of896# the Software.897898# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO899# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL900# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION901# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER902# DEALINGS IN THE SOFTWARE.903904import os905import subprocess906import argparse907import bittensor as bt908from .logging import setup_events_logger909910911def is_cuda_available():912 try:913 output = subprocess.check_output(914 ["nvidia-smi", "-L"], stderr=subprocess.STDOUT915 )916 if "NVIDIA" in output.decode("utf-8"):917 return "cuda"918 except Exception:919 pass920 try:921 output = subprocess.check_output(["nvcc", "--version"]).decode("utf-8")922 if "release" in output:923 return "cuda"924 except Exception:925 pass926 return "cpu"927928929def check_config(cls, config: "bt.Config"):930 r"""Checks/validates the config namespace object."""931 bt.logging.check_config(config)932933 full_path = os.path.expanduser(934 "{}/{}/{}/netuid{}/{}".format(935 config.logging.logging_dir, # TODO: change from ~/.bittensor/miners to ~/.bittensor/neurons936 config.wallet.name,937 config.wallet.hotkey,938 config.netuid,939 config.neuron.name,940 )941 )942 print("full path:", full_path)943 config.neuron.full_path = os.path.expanduser(full_path)944 if not os.path.exists(config.neuron.full_path):945 os.makedirs(config.neuron.full_path, exist_ok=True)946947 if not config.neuron.dont_save_events:948 # Add custom event logger for the events.949 events_logger = setup_events_logger(950 config.neuron.full_path, config.neuron.events_retention_size951 )952 bt.logging.register_primary_logger(events_logger.name)953954955def add_args(cls, parser):956 """957 Adds relevant arguments to the parser for operation.958 """959960 parser.add_argument("--netuid", type=int, help="Subnet netuid", default=1)961962 parser.add_argument(963 "--neuron.device",964 type=str,965 help="Device to run on.",966 default=is_cuda_available(),967 )968969 parser.add_argument(970 "--neuron.epoch_length",971 type=int,972 help="The default epoch length (how often we set weights, measured in 12 second blocks).",973 default=360,974 )975976 parser.add_argument(977 "--mock",978 action="store_true",979 help="Mock neuron and all network components.",980 default=False,981 )982983 parser.add_argument(984 "--neuron.events_retention_size",985 type=str,986 help="Events retention size.",987 default=2 * 1024 * 1024 * 1024, # 2 GB988 )989990 parser.add_argument(991 "--neuron.dont_save_events",992 action="store_true",993 help="If set, we dont save events to a log file.",994 default=False,995 )996997 parser.add_argument(998 "--wandb.off",999 action="store_true",1000 help="Turn off wandb.",1001 default=False,1002 )10031004 parser.add_argument(1005 "--wandb.offline",1006 action="store_true",1007 help="Runs wandb in offline mode.",1008 default=False,1009 )10101011 parser.add_argument(1012 "--wandb.notes",1013 type=str,1014 help="Notes to add to the wandb run.",1015 default="",1016 )101710181019def add_miner_args(cls, parser):1020 """Add miner specific arguments to the parser."""10211022 parser.add_argument(1023 "--neuron.name",1024 type=str,1025 help="Trials for this neuron go in neuron.root / (wallet_cold - wallet_hot) / neuron.name. ",1026 default="miner",1027 )10281029 parser.add_argument(1030 "--blacklist.force_validator_permit",1031 action="store_true",1032 help="If set, we will force incoming requests to have a permit.",1033 default=False,1034 )10351036 parser.add_argument(1037 "--blacklist.allow_non_registered",1038 action="store_true",1039 help="If set, miners will accept queries from non registered entities. (Dangerous!)",1040 default=False,1041 )10421043 parser.add_argument(1044 "--wandb.project_name",1045 type=str,1046 default="sybil-miners",1047 help="Wandb project to log to.",1048 )10491050 parser.add_argument(1051 "--wandb.entity",1052 type=str,1053 default="opentensor-dev",1054 help="Wandb entity to log to.",1055 )1056 1057 parser.add_argument(1058 "--miner.server",1059 type=str,1060 help="The url of the miner server.",1061 default="http://127.0.0.1:3001",1062 )106310641065def add_validator_args(cls, parser):1066 """Add validator specific arguments to the parser."""10671068 parser.add_argument(1069 "--neuron.name",1070 type=str,1071 help="Trials for this neuron go in neuron.root / (wallet_cold - wallet_hot) / neuron.name. ",1072 default="validator",1073 )10741075 parser.add_argument(1076 "--neuron.timeout",1077 type=float,1078 help="The timeout for each forward call in seconds.",1079 default=60,1080 )10811082 parser.add_argument(1083 "--neuron.num_concurrent_forwards",1084 type=int,1085 help="The number of concurrent forwards running at any time.",1086 default=1,1087 )10881089 parser.add_argument(1090 "--neuron.sample_size",1091 type=int,1092 help="The number of miners to query in a single step.",1093 default=10,1094 )10951096 parser.add_argument(1097 "--neuron.disable_set_weights",1098 action="store_true",1099 help="Disables setting weights.",1100 default=False,1101 )11021103 parser.add_argument(1104 "--neuron.moving_average_alpha",1105 type=float,1106 help="Moving average alpha parameter, how much to add of the new observation.",1107 default=0.1,1108 )11091110 parser.add_argument(1111 "--neuron.axon_off",1112 "--axon_off",1113 action="store_true",1114 # Note: the validator needs to serve an Axon with their IP or they may1115 # be blacklisted by the firewall of serving peers on the network.1116 help="Set this flag to not attempt to serve an Axon.",1117 default=False,1118 )11191120 parser.add_argument(1121 "--neuron.vpermit_tao_limit",1122 type=int,1123 help="The maximum number of TAO allowed to query a validator with a vpermit.",1124 default=4096,1125 )11261127 parser.add_argument(1128 "--wandb.project_name",1129 type=str,1130 help="The name of the project where you are sending the new run.",1131 default="sybil-validators",1132 )11331134 parser.add_argument(1135 "--wandb.entity",1136 type=str,1137 help="The name of the project where you are sending the new run.",1138 default="opentensor-dev",1139 )11401141 parser.add_argument(1142 "--validator_server_url",1143 type=str,1144 help="The url of the validator server.",1145 default="http://127.0.0.1:3000",1146 )114711481149def config(cls):1150 """1151 Returns the configuration object specific to this miner or validator after adding relevant arguments.1152 """1153 parser = argparse.ArgumentParser()1154 bt.wallet.add_args(parser)1155 bt.subtensor.add_args(parser)1156 bt.logging.add_args(parser)1157 bt.axon.add_args(parser)1158 cls.add_args(parser)1159 return bt.config(parser)116011611162---1163target_repo/sybil/utils/logging.py1164---1165import os1166import logging1167from logging.handlers import RotatingFileHandler11681169EVENTS_LEVEL_NUM = 381170DEFAULT_LOG_BACKUP_COUNT = 10117111721173def setup_events_logger(full_path, events_retention_size):1174 logging.addLevelName(EVENTS_LEVEL_NUM, "EVENT")11751176 logger = logging.getLogger("event")1177 logger.setLevel(EVENTS_LEVEL_NUM)11781179 def event(self, message, *args, **kws):1180 if self.isEnabledFor(EVENTS_LEVEL_NUM):1181 self._log(EVENTS_LEVEL_NUM, message, args, **kws)11821183 logging.Logger.event = event11841185 formatter = logging.Formatter(1186 "%(asctime)s | %(levelname)s | %(message)s",1187 datefmt="%Y-%m-%d %H:%M:%S",1188 )11891190 file_handler = RotatingFileHandler(1191 os.path.join(full_path, "events.log"),1192 maxBytes=events_retention_size,1193 backupCount=DEFAULT_LOG_BACKUP_COUNT,1194 )1195 file_handler.setFormatter(formatter)1196 file_handler.setLevel(EVENTS_LEVEL_NUM)1197 logger.addHandler(file_handler)11981199 return logger120012011202---1203target_repo/sybil/utils/misc.py1204---1205# The MIT License (MIT)1206# Copyright Β© 2023 Yuma Rao1207# Copyright Β© 2023 Opentensor Foundation12081209# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated1210# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation1211# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,1212# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:12131214# The above copyright notice and this permission notice shall be included in all copies or substantial portions of1215# the Software.12161217# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO1218# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL1219# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION1220# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER1221# DEALINGS IN THE SOFTWARE.12221223import time1224import math1225import hashlib as rpccheckhealth1226from math import floor1227from typing import Callable, Any1228from functools import lru_cache, update_wrapper122912301231# LRU Cache with TTL1232def ttl_cache(maxsize: int = 128, typed: bool = False, ttl: int = -1):1233 """1234 Decorator that creates a cache of the most recently used function calls with a time-to-live (TTL) feature.1235 The cache evicts the least recently used entries if the cache exceeds the `maxsize` or if an entry has1236 been in the cache longer than the `ttl` period.12371238 Args:1239 maxsize (int): Maximum size of the cache. Once the cache grows to this size, subsequent entries1240 replace the least recently used ones. Defaults to 128.1241 typed (bool): If set to True, arguments of different types will be cached separately. For example,1242 f(3) and f(3.0) will be treated as distinct calls with distinct results. Defaults to False.1243 ttl (int): The time-to-live for each cache entry, measured in seconds. If set to a non-positive value,1244 the TTL is set to a very large number, effectively making the cache entries permanent. Defaults to -1.12451246 Returns:1247 Callable: A decorator that can be applied to functions to cache their return values.12481249 The decorator is useful for caching results of functions that are expensive to compute and are called1250 with the same arguments frequently within short periods of time. The TTL feature helps in ensuring1251 that the cached values are not stale.12521253 Example:1254 @ttl_cache(ttl=10)1255 def get_data(param):1256 # Expensive data retrieval operation1257 return data1258 """1259 if ttl <= 0:1260 ttl = 655361261 hash_gen = _ttl_hash_gen(ttl)12621263 def wrapper(func: Callable) -> Callable:1264 @lru_cache(maxsize, typed)1265 def ttl_func(ttl_hash, *args, **kwargs):1266 return func(*args, **kwargs)12671268 def wrapped(*args, **kwargs) -> Any:1269 th = next(hash_gen)1270 return ttl_func(th, *args, **kwargs)12711272 return update_wrapper(wrapped, func)12731274 return wrapper127512761277def _ttl_hash_gen(seconds: int):1278 """1279 Internal generator function used by the `ttl_cache` decorator to generate a new hash value at regular1280 time intervals specified by `seconds`.12811282 Args:1283 seconds (int): The number of seconds after which a new hash value will be generated.12841285 Yields:1286 int: A hash value that represents the current time interval.12871288 This generator is used to create time-based hash values that enable the `ttl_cache` to determine1289 whether cached entries are still valid or if they have expired and should be recalculated.1290 """1291 start_time = time.time()1292 while True:1293 yield floor((time.time() - start_time) / seconds)129412951296# 12 seconds updating block.1297@ttl_cache(maxsize=1, ttl=12)1298def ttl_get_block(self) -> int:1299 """1300 Retrieves the current block number from the blockchain. This method is cached with a time-to-live (TTL)1301 of 12 seconds, meaning that it will only refresh the block number from the blockchain at most every 12 seconds,1302 reducing the number of calls to the underlying blockchain interface.13031304 Returns:1305 int: The current block number on the blockchain.13061307 This method is useful for applications that need to access the current block number frequently and can1308 tolerate a delay of up to 12 seconds for the latest information. By using a cache with TTL, the method1309 efficiently reduces the workload on the blockchain interface.13101311 Example:1312 current_block = ttl_get_block(self)13131314 Note: self here is the miner or validator instance1315 """1316 return self.subtensor.get_current_block()131713181319---1320target_repo/sybil/utils/uids.py1321---1322import random1323import bittensor as bt1324import numpy as np1325from typing import List132613271328def check_uid_availability(1329 metagraph: "bt.metagraph.Metagraph", uid: int, vpermit_tao_limit: int1330) -> bool:1331 """Check if uid is available. The UID should be available if it is serving and has less than vpermit_tao_limit stake1332 Args:1333 metagraph (:obj: bt.metagraph.Metagraph): Metagraph object1334 uid (int): uid to be checked1335 vpermit_tao_limit (int): Validator permit tao limit1336 Returns:1337 bool: True if uid is available, False otherwise1338 """1339 # # Filter non serving axons.1340 if not metagraph.axons[uid].is_serving:1341 return False1342 # # Filter validator permit > 1024 stake.1343 # if metagraph.validator_permit[uid]:1344 # if metagraph.S[uid] > vpermit_tao_limit:1345 # return False1346 # # Available otherwise.1347 # return True1348 return True134913501351def get_random_uids(self, k: int, exclude: List[int] = None) -> np.ndarray:1352 """Returns k available random uids from the metagraph.1353 Args:1354 k (int): Number of uids to return.1355 exclude (List[int]): List of uids to exclude from the random sampling.1356 Returns:1357 uids (np.ndarray): Randomly sampled available uids.1358 Notes:1359 If `k` is larger than the number of available `uids`, set `k` to the number of available `uids`.1360 """1361 candidate_uids = []1362 avail_uids = []13631364 for uid in range(self.metagraph.n.item()):1365 uid_is_available = check_uid_availability(1366 self.metagraph, uid, self.config.neuron.vpermit_tao_limit1367 )1368 uid_is_not_excluded = exclude is None or uid not in exclude13691370 if uid_is_available:1371 avail_uids.append(uid)1372 if uid_is_not_excluded:1373 candidate_uids.append(uid)1374 # If k is larger than the number of available uids, set k to the number of available uids.1375 k = min(k, len(avail_uids))1376 # Remove uids with duplicate IPs1377 unique_ips = set()1378 unique_candidate_uids = []1379 for uid in candidate_uids:1380 ip = self.metagraph.axons[uid].ip1381 if ip not in unique_ips:1382 unique_ips.add(ip)1383 unique_candidate_uids.append(uid)1384 # Check if candidate_uids contain enough for querying, if not grab all avaliable uids1385 available_uids = unique_candidate_uids1386 if len(unique_candidate_uids) < k:1387 available_uids += random.sample(1388 [uid for uid in avail_uids if uid not in candidate_uids],1389 k - len(candidate_uids),1390 )1391 uids = np.array(random.sample(available_uids, k))1392 return uids139313941395---1396target_repo/sybil/api/__init__.py1397---139813991400---1401target_repo/sybil/api/dummy.py1402---1403# The MIT License (MIT)1404# Copyright Β© 2021 Yuma Rao1405# Copyright Β© 2023 Opentensor Foundation1406# Copyright Β© 2023 Opentensor Technologies Inc14071408# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated1409# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation1410# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,1411# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:14121413# The above copyright notice and this permission notice shall be included in all copies or substantial portions of1414# the Software.14151416# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO1417# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL1418# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION1419# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER1420# DEALINGS IN THE SOFTWARE.14211422import bittensor as bt1423from typing import List, Optional, Union, Any, Dict1424from sybil.protocol import Dummy1425from bittensor.subnets import SubnetsAPI142614271428class DummyAPI(SubnetsAPI):1429 def __init__(self, wallet: "bt.wallet"):1430 super().__init__(wallet)1431 self.netuid = 331432 self.name = "dummy"14331434 def prepare_synapse(self, dummy_input: int) -> Dummy:1435 synapse.dummy_input = dummy_input1436 return synapse14371438 def process_responses(1439 self, responses: List[Union["bt.Synapse", Any]]1440 ) -> List[int]:1441 outputs = []1442 for response in responses:1443 if response.dendrite.status_code != 200:1444 continue1445 return outputs.append(response.dummy_output)1446 return outputs144714481449---1450target_repo/sybil/api/get_query_axons.py1451---1452# The MIT License (MIT)1453# Copyright Β© 2021 Yuma Rao1454# Copyright Β© 2023 Opentensor Foundation1455# Copyright Β© 2023 Opentensor Technologies Inc14561457# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated1458# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation1459# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,1460# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:14611462# The above copyright notice and this permission notice shall be included in all copies or substantial portions of1463# the Software.14641465# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO1466# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL1467# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION1468# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER1469# DEALINGS IN THE SOFTWARE.1470import numpy as np1471import random1472import bittensor as bt147314741475async def ping_uids(dendrite, metagraph, uids, timeout=3):1476 """1477 Pings a list of UIDs to check their availability on the Bittensor network.14781479 Args:1480 dendrite (bittensor.dendrite): The dendrite instance to use for pinging nodes.1481 metagraph (bittensor.metagraph): The metagraph instance containing network information.1482 uids (list): A list of UIDs (unique identifiers) to ping.1483 timeout (int, optional): The timeout in seconds for each ping. Defaults to 3.14841485 Returns:1486 tuple: A tuple containing two lists:1487 - The first list contains UIDs that were successfully pinged.1488 - The second list contains UIDs that failed to respond.1489 """1490 axons = [metagraph.axons[uid] for uid in uids]1491 try:1492 responses = await dendrite(1493 axons,1494 bt.Synapse(), # TODO: potentially get the synapses available back?1495 deserialize=False,1496 timeout=timeout,1497 )1498 successful_uids = [1499 uid1500 for uid, response in zip(uids, responses)1501 if response.dendrite.status_code == 2001502 ]1503 failed_uids = [1504 uid1505 for uid, response in zip(uids, responses)1506 if response.dendrite.status_code != 2001507 ]1508 except Exception as e:1509 bt.logging.error(f"Dendrite ping failed: {e}")1510 successful_uids = []1511 failed_uids = uids1512 bt.logging.debug(f"ping() successful uids: {successful_uids}")1513 bt.logging.debug(f"ping() failed uids : {failed_uids}")1514 return successful_uids, failed_uids151515161517async def get_query_api_nodes(dendrite, metagraph, n=0.1, timeout=3):1518 """1519 Fetches the available API nodes to query for the particular subnet.15201521 Args:1522 wallet (bittensor.wallet): The wallet instance to use for querying nodes.1523 metagraph (bittensor.metagraph): The metagraph instance containing network information.1524 n (float, optional): The fraction of top nodes to consider based on stake. Defaults to 0.1.1525 timeout (int, optional): The timeout in seconds for pinging nodes. Defaults to 3.15261527 Returns:1528 list: A list of UIDs representing the available API nodes.1529 """1530 bt.logging.debug(1531 f"Fetching available API nodes for subnet {metagraph.netuid}"1532 )1533 vtrust_uids = [1534 uid.item()1535 for uid in metagraph.uids1536 if metagraph.validator_trust[uid] > 01537 ]1538 top_uids = np.where(metagraph.S > np.quantile(metagraph.S, 1 - n))[1539 01540 ].tolist()1541 init_query_uids = set(top_uids).intersection(set(vtrust_uids))1542 query_uids, _ = await ping_uids(1543 dendrite, metagraph, list(init_query_uids), timeout=timeout1544 )1545 bt.logging.debug(1546 f"Available API node UIDs for subnet {metagraph.netuid}: {query_uids}"1547 )1548 if len(query_uids) > 3:1549 query_uids = random.sample(query_uids, 3)1550 return query_uids155115521553async def get_query_api_axons(1554 wallet, metagraph=None, n=0.1, timeout=3, uids=None1555):1556 """1557 Retrieves the axons of query API nodes based on their availability and stake.15581559 Args:1560 wallet (bittensor.wallet): The wallet instance to use for querying nodes.1561 metagraph (bittensor.metagraph, optional): The metagraph instance containing network information.1562 n (float, optional): The fraction of top nodes to consider based on stake. Defaults to 0.1.1563 timeout (int, optional): The timeout in seconds for pinging nodes. Defaults to 3.1564 uids (Union[List[int], int], optional): The specific UID(s) of the API node(s) to query. Defaults to None.15651566 Returns:1567 list: A list of axon objects for the available API nodes.1568 """1569 dendrite = bt.dendrite(wallet=wallet)15701571 if metagraph is None:1572 metagraph = bt.metagraph(netuid=21)15731574 if uids is not None:1575 query_uids = [uids] if isinstance(uids, int) else uids1576 else:1577 query_uids = await get_query_api_nodes(1578 dendrite, metagraph, n=n, timeout=timeout1579 )1580 return [metagraph.axons[uid] for uid in query_uids]158115821583---1584target_repo/sybil/base/__init__.py1585---158615871588---1589target_repo/sybil/base/miner.py1590---1591# The MIT License (MIT)1592# Copyright Β© 2023 Yuma Rao15931594# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated1595# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation1596# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,1597# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:15981599# The above copyright notice and this permission notice shall be included in all copies or substantial portions of1600# the Software.16011602# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO1603# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL1604# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION1605# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER1606# DEALINGS IN THE SOFTWARE.16071608import time1609import asyncio1610import threading1611import argparse1612import traceback16131614import bittensor as bt16151616from sybil.base.neuron import BaseNeuron1617from sybil.utils.config import add_miner_args16181619from typing import Union162016211622class BaseMinerNeuron(BaseNeuron):1623 """1624 Base class for Bittensor miners.1625 """16261627 neuron_type: str = "MinerNeuron"16281629 @classmethod1630 def add_args(cls, parser: argparse.ArgumentParser):1631 super().add_args(parser)1632 add_miner_args(cls, parser)16331634 def __init__(self, config=None):1635 super().__init__(config=config)16361637 # Warn if allowing incoming requests from anyone.1638 if not self.config.blacklist.force_validator_permit:1639 bt.logging.warning(1640 "You are allowing non-validators to send requests to your miner. This is a security risk."1641 )1642 if self.config.blacklist.allow_non_registered:1643 bt.logging.warning(1644 "You are allowing non-registered entities to send requests to your miner. This is a security risk."1645 )1646 # The axon handles request processing, allowing validators to send this miner requests.1647 self.axon = bt.axon(1648 wallet=self.wallet,1649 config=self.config() if callable(self.config) else self.config,1650 )16511652 self.miner_server = self.config.miner.server16531654 # Attach determiners which functions are called when servicing a request.1655 bt.logging.info(f"Attaching forward function to miner axon.")1656 self.axon.attach(1657 forward_fn=self.forward,1658 blacklist_fn=self.blacklist,1659 priority_fn=self.priority,1660 )1661 bt.logging.info(f"Axon created: {self.axon}")16621663 # Instantiate runners1664 self.should_exit: bool = False1665 self.is_running: bool = False1666 self.thread: Union[threading.Thread, None] = None1667 self.lock = asyncio.Lock()16681669 def run(self):1670 """1671 Initiates and manages the main loop for the miner on the Bittensor network. The main loop handles graceful shutdown on keyboard interrupts and logs unforeseen errors.16721673 This function performs the following primary tasks:1674 1. Check for registration on the Bittensor network.1675 2. Starts the miner's axon, making it active on the network.1676 3. Periodically resynchronizes with the chain; updating the metagraph with the latest network state and setting weights.16771678 The miner continues its operations until `should_exit` is set to True or an external interruption occurs.1679 During each epoch of its operation, the miner waits for new blocks on the Bittensor network, updates its1680 knowledge of the network (metagraph), and sets its weights. This process ensures the miner remains active1681 and up-to-date with the network's latest state.16821683 Note:1684 - The function leverages the global configurations set during the initialization of the miner.1685 - The miner's axon serves as its interface to the Bittensor network, handling incoming and outgoing requests.16861687 Raises:1688 KeyboardInterrupt: If the miner is stopped by a manual interruption.1689 Exception: For unforeseen errors during the miner's operation, which are logged for diagnosis.1690 """16911692 # Check that miner is registered on the network.1693 self.sync()16941695 # Serve passes the axon information to the network + netuid we are hosting on.1696 # This will auto-update if the axon port of external ip have changed.1697 bt.logging.info(1698 f"Serving miner axon {self.axon} on network: {self.config.subtensor.chain_endpoint} with netuid: {self.config.netuid}"1699 )1700 self.axon.serve(netuid=self.config.netuid, subtensor=self.subtensor)17011702 # Start starts the miner's axon, making it active on the network.1703 self.axon.start()17041705 bt.logging.info(f"Miner starting at block: {self.block}")17061707 # This loop maintains the miner's operations until intentionally stopped.1708 try:1709 while not self.should_exit:1710 while (1711 self.block - self.metagraph.last_update[self.uid]1712 < self.config.neuron.epoch_length1713 ):1714 # Wait before checking again.1715 time.sleep(1)17161717 # Check if we should exit.1718 if self.should_exit:1719 break17201721 # Sync metagraph and potentially set weights.1722 self.sync()1723 self.step += 117241725 # If someone intentionally stops the miner, it'll safely terminate operations.1726 except KeyboardInterrupt:1727 self.axon.stop()1728 bt.logging.success("Miner killed by keyboard interrupt.")1729 exit()17301731 # In case of unforeseen errors, the miner will log the error and continue operations.1732 except Exception as e:1733 bt.logging.error(traceback.format_exc())17341735 def run_in_background_thread(self):1736 """1737 Starts the miner's operations in a separate background thread.1738 This is useful for non-blocking operations.1739 """1740 if not self.is_running:1741 bt.logging.debug("Starting miner in background thread.")1742 self.should_exit = False1743 self.thread = threading.Thread(target=self.run, daemon=True)1744 self.thread.start()1745 self.is_running = True1746 bt.logging.debug("Started")17471748 def stop_run_thread(self):1749 """1750 Stops the miner's operations that are running in the background thread.1751 """1752 if self.is_running:1753 bt.logging.debug("Stopping miner in background thread.")1754 self.should_exit = True1755 if self.thread is not None:1756 self.thread.join(5)1757 self.is_running = False1758 bt.logging.debug("Stopped")17591760 def __enter__(self):1761 """1762 Starts the miner's operations in a background thread upon entering the context.1763 This method facilitates the use of the miner in a 'with' statement.1764 """1765 self.run_in_background_thread()1766 return self17671768 def __exit__(self, exc_type, exc_value, traceback):1769 """1770 Stops the miner's background operations upon exiting the context.1771 This method facilitates the use of the miner in a 'with' statement.17721773 Args:1774 exc_type: The type of the exception that caused the context to be exited.1775 None if the context was exited without an exception.1776 exc_value: The instance of the exception that caused the context to be exited.1777 None if the context was exited without an exception.1778 traceback: A traceback object encoding the stack trace.1779 None if the context was exited without an exception.1780 """1781 self.stop_run_thread()17821783 def resync_metagraph(self):1784 """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph."""1785 bt.logging.info("resync_metagraph()")17861787 # Sync the metagraph.1788 self.metagraph.sync(subtensor=self.subtensor)178917901791---1792target_repo/sybil/base/neuron.py1793---1794# The MIT License (MIT)1795# Copyright Β© 2023 Yuma Rao17961797# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated1798# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation1799# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,1800# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:18011802# The above copyright notice and this permission notice shall be included in all copies or substantial portions of1803# the Software.18041805# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO1806# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL1807# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION1808# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER1809# DEALINGS IN THE SOFTWARE.18101811import copy1812import typing18131814import bittensor as bt18151816from abc import ABC, abstractmethod18171818# Sync calls set weights and also resyncs the metagraph.1819from sybil.utils.config import check_config, add_args, config1820from sybil.utils.misc import ttl_get_block1821from sybil import __spec_version__ as spec_version1822from sybil.mock import MockSubtensor, MockMetagraph182318241825class BaseNeuron(ABC):1826 """1827 Base class for Bittensor miners. This class is abstract and should be inherited by a subclass. It contains the core logic for all neurons; validators and miners.18281829 In addition to creating a wallet, subtensor, and metagraph, this class also handles the synchronization of the network state via a basic checkpointing mechanism based on epoch length.1830 """18311832 neuron_type: str = "BaseNeuron"18331834 @classmethod1835 def check_config(cls, config: "bt.Config"):1836 check_config(cls, config)18371838 @classmethod1839 def add_args(cls, parser):1840 add_args(cls, parser)18411842 @classmethod1843 def config(cls):1844 return config(cls)18451846 subtensor: "bt.subtensor"1847 wallet: "bt.wallet"1848 metagraph: "bt.metagraph"1849 spec_version: int = spec_version18501851 @property1852 def block(self):1853 return ttl_get_block(self)18541855 def __init__(self, config=None):1856 base_config = copy.deepcopy(config or BaseNeuron.config())1857 self.config = self.config()1858 self.config.merge(base_config)1859 self.check_config(self.config)18601861 # Set up logging with the provided configuration.1862 bt.logging.set_config(config=self.config.logging)18631864 # If a gpu is required, set the device to cuda:N (e.g. cuda:0)1865 self.device = self.config.neuron.device18661867 # Log the configuration for reference.1868 bt.logging.info(self.config)18691870 # Build Bittensor objects1871 # These are core Bittensor classes to interact with the network.1872 bt.logging.info("Setting up bittensor objects.")18731874 # The wallet holds the cryptographic key pairs for the miner.1875 if self.config.mock:1876 self.wallet = bt.MockWallet(config=self.config)1877 self.subtensor = MockSubtensor(1878 self.config.netuid, wallet=self.wallet1879 )1880 self.metagraph = MockMetagraph(1881 self.config.netuid, subtensor=self.subtensor1882 )1883 else:1884 self.wallet = bt.wallet(config=self.config)1885 self.subtensor = bt.subtensor(config=self.config)1886 self.metagraph = self.subtensor.metagraph(self.config.netuid)18871888 bt.logging.info(f"Wallet: {self.wallet}")1889 bt.logging.info(f"Subtensor: {self.subtensor}")1890 bt.logging.info(f"Metagraph: {self.metagraph}")1891 1892 self.validator_server_url = self.config.validator_server_url18931894 # Check if the miner is registered on the Bittensor network before proceeding further.1895 self.check_registered()18961897 # Each miner gets a unique identity (UID) in the network for differentiation.1898 self.uid = self.metagraph.hotkeys.index(1899 self.wallet.hotkey.ss58_address1900 )1901 bt.logging.info(1902 f"Running neuron on subnet: {self.config.netuid} with uid {self.uid} using network: {self.subtensor.chain_endpoint}"1903 )1904 self.step = 019051906 @abstractmethod1907 async def forward(self, synapse: bt.Synapse) -> bt.Synapse:1908 ...19091910 @abstractmethod1911 def run(self):1912 ...19131914 def sync(self):1915 """1916 Wrapper for synchronizing the state of the network for the given miner or validator.1917 """1918 # Ensure miner or validator hotkey is still registered on the network.1919 self.check_registered()19201921 if self.should_sync_metagraph():1922 self.resync_metagraph()19231924 if self.should_set_weights():1925 self.set_weights()19261927 # Always save state.1928 self.save_state()19291930 def check_registered(self):1931 # --- Check for registration.1932 if not self.subtensor.is_hotkey_registered(1933 netuid=self.config.netuid,1934 hotkey_ss58=self.wallet.hotkey.ss58_address,1935 ):1936 bt.logging.error(1937 f"Wallet: {self.wallet} is not registered on netuid {self.config.netuid}."1938 f" Please register the hotkey using `btcli subnets register` before trying again"1939 )1940 exit()19411942 def should_sync_metagraph(self):1943 """1944 Check if enough epoch blocks have elapsed since the last checkpoint to sync.1945 """1946 return (1947 self.block - self.metagraph.last_update[self.uid]1948 ) > self.config.neuron.epoch_length19491950 def should_set_weights(self) -> bool:1951 # Don't set weights on initialization.1952 if self.step == 0:1953 return False19541955 # Check if enough epoch blocks have elapsed since the last epoch.1956 if self.config.neuron.disable_set_weights:1957 return False19581959 # Define appropriate logic for when set weights.1960 return (1961 (self.block - self.metagraph.last_update[self.uid])1962 > self.config.neuron.epoch_length1963 and self.neuron_type != "MinerNeuron"1964 ) # don't set weights if you're a miner19651966 def save_state(self):1967 # bt.logging.warning(1968 # "save_state() not implemented for this neuron. You can implement this function to save model checkpoints or other useful data."1969 # )1970 pass19711972 def load_state(self):1973 bt.logging.warning(1974 "load_state() not implemented for this neuron. You can implement this function to load model checkpoints or other useful data."1975 )197619771978---1979target_repo/sybil/base/validator.py1980---1981# The MIT License (MIT)1982# Copyright Β© 2023 Yuma Rao1983# TODO(developer): Set your name1984# Copyright Β© 2023 <your name>19851986# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated1987# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation1988# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,1989# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:19901991# The above copyright notice and this permission notice shall be included in all copies or substantial portions of1992# the Software.19931994# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO1995# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL1996# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION1997# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER1998# DEALINGS IN THE SOFTWARE.199920002001import copy2002import numpy as np2003import asyncio2004import argparse2005import threading2006import bittensor as bt20072008from typing import List, Union2009from traceback import print_exception20102011from sybil.base.neuron import BaseNeuron2012from sybil.base.utils.weight_utils import (2013 process_weights_for_netuid,2014 convert_weights_and_uids_for_emit,2015) # TODO: Replace when bittensor switches to numpy2016from sybil.mock import MockDendrite2017from sybil.utils.config import add_validator_args201820192020class BaseValidatorNeuron(BaseNeuron):2021 """2022 Base class for Bittensor validators. Your validator should inherit from this class.2023 """20242025 neuron_type: str = "ValidatorNeuron"20262027 @classmethod2028 def add_args(cls, parser: argparse.ArgumentParser):2029 super().add_args(parser)2030 add_validator_args(cls, parser)20312032 def __init__(self, config=None):2033 super().__init__(config=config)20342035 # Save a copy of the hotkeys to local memory.2036 self.hotkeys = copy.deepcopy(self.metagraph.hotkeys)20372038 # Dendrite lets us send messages to other nodes (axons) in the network.2039 if self.config.mock:2040 self.dendrite = MockDendrite(wallet=self.wallet)2041 else:2042 self.dendrite = bt.dendrite(wallet=self.wallet)2043 bt.logging.info(f"Dendrite: {self.dendrite}")20442045 # Set up initial scoring weights for validation2046 bt.logging.info("Building validation weights.")2047 self.scores = np.zeros(self.metagraph.n, dtype=np.float32)20482049 # Init sync with the network. Updates the metagraph.2050 self.sync()20512052 # # Serve axon to enable external connections.2053 # if not self.config.neuron.axon_off:2054 # self.serve_axon()2055 # else:2056 # bt.logging.warning("axon off, not serving ip to chain.")20572058 # Create asyncio event loop to manage async tasks.2059 self.loop = asyncio.get_event_loop()20602061 # Instantiate runners2062 self.should_exit: bool = False2063 self.is_running: bool = False2064 self.thread: Union[threading.Thread, None] = None2065 self.lock = asyncio.Lock()20662067 def serve_axon(self):2068 """Serve axon to enable external connections."""20692070 bt.logging.info("serving ip to chain...")2071 try:2072 self.axon = bt.axon(wallet=self.wallet, config=self.config)20732074 try:2075 self.subtensor.serve_axon(2076 netuid=self.config.netuid,2077 axon=self.axon,2078 )2079 bt.logging.info(2080 f"Running validator {self.axon} on network: {self.config.subtensor.chain_endpoint} with netuid: {self.config.netuid}"2081 )2082 except Exception as e:2083 bt.logging.error(f"Failed to serve Axon with exception: {e}")2084 pass20852086 except Exception as e:2087 bt.logging.error(2088 f"Failed to create Axon initialize with exception: {e}"2089 )2090 pass20912092 async def concurrent_forward(self):2093 coroutines = [2094 self.forward()2095 for _ in range(self.config.neuron.num_concurrent_forwards)2096 ]2097 await asyncio.gather(*coroutines)20982099 def run(self):2100 """2101 Initiates and manages the main loop for the miner on the Bittensor network. The main loop handles graceful shutdown on keyboard interrupts and logs unforeseen errors.21022103 This function performs the following primary tasks:2104 1. Check for registration on the Bittensor network.2105 2. Continuously forwards queries to the miners on the network, rewarding their responses and updating the scores accordingly.2106 3. Periodically resynchronizes with the chain; updating the metagraph with the latest network state and setting weights.21072108 The essence of the validator's operations is in the forward function, which is called every step. The forward function is responsible for querying the network and scoring the responses.21092110 Note:2111 - The function leverages the global configurations set during the initialization of the miner.2112 - The miner's axon serves as its interface to the Bittensor network, handling incoming and outgoing requests.21132114 Raises:2115 KeyboardInterrupt: If the miner is stopped by a manual interruption.2116 Exception: For unforeseen errors during the miner's operation, which are logged for diagnosis.2117 """21182119 # Check that validator is registered on the network.2120 self.sync()21212122 bt.logging.info(f"Validator starting at block: {self.block}")21232124 # This loop maintains the validator's operations until intentionally stopped.2125 try:2126 while True:2127 bt.logging.info(f"step({self.step}) block({self.block})")21282129 # Run multiple forwards concurrently.2130 self.loop.run_until_complete(self.concurrent_forward())21312132 # Check if we should exit.2133 if self.should_exit:2134 break21352136 # Sync metagraph and potentially set weights.2137 self.sync()21382139 self.step += 121402141 # If someone intentionally stops the validator, it'll safely terminate operations.2142 except KeyboardInterrupt:2143 self.axon.stop()2144 bt.logging.success("Validator killed by keyboard interrupt.")2145 exit()21462147 # In case of unforeseen errors, the validator will log the error and continue operations.2148 except Exception as err:2149 bt.logging.error(f"Error during validation: {str(err)}")2150 bt.logging.debug(2151 str(print_exception(type(err), err, err.__traceback__))2152 )21532154 def run_in_background_thread(self):2155 """2156 Starts the validator's operations in a background thread upon entering the context.2157 This method facilitates the use of the validator in a 'with' statement.2158 """2159 if not self.is_running:2160 bt.logging.debug("Starting validator in background thread.")2161 self.should_exit = False2162 self.thread = threading.Thread(target=self.run, daemon=True)2163 self.thread.start()2164 self.is_running = True2165 bt.logging.debug("Started")21662167 def stop_run_thread(self):2168 """2169 Stops the validator's operations that are running in the background thread.2170 """2171 if self.is_running:2172 bt.logging.debug("Stopping validator in background thread.")2173 self.should_exit = True2174 self.thread.join(5)2175 self.is_running = False2176 bt.logging.debug("Stopped")21772178 def __enter__(self):2179 self.run_in_background_thread()2180 return self21812182 def __exit__(self, exc_type, exc_value, traceback):2183 """2184 Stops the validator's background operations upon exiting the context.2185 This method facilitates the use of the validator in a 'with' statement.21862187 Args:2188 exc_type: The type of the exception that caused the context to be exited.2189 None if the context was exited without an exception.2190 exc_value: The instance of the exception that caused the context to be exited.2191 None if the context was exited without an exception.2192 traceback: A traceback object encoding the stack trace.2193 None if the context was exited without an exception.2194 """2195 if self.is_running:2196 bt.logging.debug("Stopping validator in background thread.")2197 self.should_exit = True2198 self.thread.join(5)2199 self.is_running = False2200 bt.logging.debug("Stopped")22012202 def set_weights(self):2203 """2204 Sets the validator weights to the metagraph hotkeys based on the scores it has received from the miners. The weights determine the trust and incentive level the validator assigns to miner nodes on the network.2205 """22062207 # Check if self.scores contains any NaN values and log a warning if it does.2208 if np.isnan(self.scores).any():2209 bt.logging.warning(2210 f"Scores contain NaN values. This may be due to a lack of responses from miners, or a bug in your reward functions."2211 )22122213 # Calculate the average reward for each uid across non-zero values.2214 # Replace any NaN values with 0.2215 # Compute the norm of the scores2216 norm = np.linalg.norm(self.scores, ord=1, axis=0, keepdims=True)22172218 # Check if the norm is zero or contains NaN values2219 if np.any(norm == 0) or np.isnan(norm).any():2220 norm = np.ones_like(norm) # Avoid division by zero or NaN22212222 # Compute raw_weights safely2223 raw_weights = self.scores / norm22242225 bt.logging.debug("raw_weights", raw_weights)2226 bt.logging.debug("raw_weight_uids", str(self.metagraph.uids.tolist()))2227 # Process the raw weights to final_weights via subtensor limitations.2228 (2229 processed_weight_uids,2230 processed_weights,2231 ) = process_weights_for_netuid(2232 uids=self.metagraph.uids,2233 weights=raw_weights,2234 netuid=self.config.netuid,2235 subtensor=self.subtensor,2236 metagraph=self.metagraph,2237 )2238 bt.logging.debug("processed_weights", processed_weights)2239 bt.logging.debug("processed_weight_uids", processed_weight_uids)22402241 # Convert to uint16 weights and uids.2242 (2243 uint_uids,2244 uint_weights,2245 ) = convert_weights_and_uids_for_emit(2246 uids=processed_weight_uids, weights=processed_weights2247 )2248 bt.logging.debug("uint_weights", uint_weights)2249 bt.logging.debug("uint_uids", uint_uids)22502251 # Set the weights on chain via our subtensor connection.2252 # Retry 20 times if it fails2253 for _ in range(20):2254 result, msg = self.subtensor.set_weights(2255 wallet=self.wallet,2256 netuid=self.config.netuid,2257 uids=uint_uids,2258 weights=uint_weights,2259 wait_for_finalization=False,2260 wait_for_inclusion=False,2261 version_key=self.spec_version,2262 )2263 if result is True:2264 bt.logging.info("set_weights on chain successfully!")2265 break2266 else:2267 bt.logging.error("set_weights failed. Retrying... ", msg)22682269 def resync_metagraph(self):2270 """Resyncs the metagraph and updates the hotkeys and moving averages based on the new metagraph."""2271 bt.logging.info("resync_metagraph()")22722273 # Copies state of metagraph before syncing.2274 previous_metagraph = copy.deepcopy(self.metagraph)22752276 # Sync the metagraph.2277 self.metagraph.sync(subtensor=self.subtensor)22782279 # Check if the metagraph axon info has changed.2280 if previous_metagraph.axons == self.metagraph.axons:2281 return22822283 bt.logging.info(2284 "Metagraph updated, re-syncing hotkeys, dendrite pool and moving averages"2285 )2286 # Zero out all hotkeys that have been replaced.2287 for uid, hotkey in enumerate(self.hotkeys):2288 if hotkey != self.metagraph.hotkeys[uid]:2289 self.scores[uid] = 0 # hotkey has been replaced22902291 # Check to see if the metagraph has changed size.2292 # If so, we need to add new hotkeys and moving averages.2293 if len(self.hotkeys) < len(self.metagraph.hotkeys):2294 # Update the size of the moving average scores.2295 new_moving_average = np.zeros((self.metagraph.n))2296 min_len = min(len(self.hotkeys), len(self.scores))2297 new_moving_average[:min_len] = self.scores[:min_len]2298 self.scores = new_moving_average22992300 # Update the hotkeys.2301 self.hotkeys = copy.deepcopy(self.metagraph.hotkeys)23022303 def update_scores(self, rewards: np.ndarray, uids: List[int]):2304 """Performs exponential moving average on the scores based on the rewards received from the miners."""23052306 # Check if rewards contains NaN values.2307 if np.isnan(rewards).any():2308 bt.logging.warning(f"NaN values detected in rewards: {rewards}")2309 # Replace any NaN values in rewards with 0.2310 rewards = np.nan_to_num(rewards, nan=0)23112312 # Ensure rewards is a numpy array.2313 rewards = np.asarray(rewards)23142315 # Check if `uids` is already a numpy array and copy it to avoid the warning.2316 if isinstance(uids, np.ndarray):2317 uids_array = uids.copy()2318 else:2319 uids_array = np.array(uids)23202321 # Handle edge case: If either rewards or uids_array is empty.2322 if rewards.size == 0 or uids_array.size == 0:2323 bt.logging.info(f"rewards: {rewards}, uids_array: {uids_array}")2324 bt.logging.warning(2325 "Either rewards or uids_array is empty. No updates will be performed."2326 )2327 return23282329 # Check if sizes of rewards and uids_array match.2330 if rewards.size != uids_array.size:2331 raise ValueError(2332 f"Shape mismatch: rewards array of shape {rewards.shape} "2333 f"cannot be broadcast to uids array of shape {uids_array.shape}"2334 )23352336 # Compute forward pass rewards, assumes uids are mutually exclusive.2337 # shape: [ metagraph.n ]2338 scattered_rewards: np.ndarray = np.zeros_like(self.scores)2339 scattered_rewards[uids_array] = rewards2340 bt.logging.debug(f"Scattered rewards: {rewards}")23412342 # Update scores with rewards produced by this step.2343 # shape: [ metagraph.n ]2344 alpha: float = self.config.neuron.moving_average_alpha2345 self.scores: np.ndarray = (2346 alpha * scattered_rewards + (1 - alpha) * self.scores2347 )2348 bt.logging.debug(f"Updated moving avg scores: {self.scores}")23492350 def save_state(self):2351 """Saves the state of the validator to a file."""2352 bt.logging.info("Saving validator state.")23532354 # Save the state of the validator to file.2355 np.savez(2356 self.config.neuron.full_path + "/state.npz",2357 step=self.step,2358 scores=self.scores,2359 hotkeys=self.hotkeys,2360 )23612362 def load_state(self):2363 """Loads the state of the validator from a file."""2364 bt.logging.info("Loading validator state.")23652366 # Load the state of the validator from file.2367 state = np.load(self.config.neuron.full_path + "/state.npz")2368 self.step = state["step"]2369 self.scores = state["scores"]2370 self.hotkeys = state["hotkeys"]237123722373---2374target_repo/sybil/base/utils/__init__.py2375---237623772378---2379target_repo/sybil/base/utils/weight_utils.py2380---2381import numpy as np2382from typing import Tuple, List, Union, Any2383import bittensor2384from numpy import ndarray, dtype, floating, complexfloating23852386U32_MAX = 42949672952387U16_MAX = 65535238823892390def normalize_max_weight(x: np.ndarray, limit: float = 0.1) -> np.ndarray:2391 r"""Normalizes the numpy array x so that sum(x) = 1 and the max value is not greater than the limit.2392 Args:2393 x (:obj:`np.ndarray`):2394 Array to be max_value normalized.2395 limit: float:2396 Max value after normalization.2397 Returns:2398 y (:obj:`np.ndarray`):2399 Normalized x array.2400 """2401 epsilon = 1e-7 # For numerical stability after normalization24022403 weights = x.copy()2404 values = np.sort(weights)24052406 if x.sum() == 0 or len(x) * limit <= 1:2407 return np.ones_like(x) / x.size2408 else:2409 estimation = values / values.sum()24102411 if estimation.max() <= limit:2412 return weights / weights.sum()24132414 # Find the cumulative sum and sorted array2415 cumsum = np.cumsum(estimation, 0)24162417 # Determine the index of cutoff2418 estimation_sum = np.array(2419 [(len(values) - i - 1) * estimation[i] for i in range(len(values))]2420 )2421 n_values = (2422 estimation / (estimation_sum + cumsum + epsilon) < limit2423 ).sum()24242425 # Determine the cutoff based on the index2426 cutoff_scale = (limit * cumsum[n_values - 1] - epsilon) / (2427 1 - (limit * (len(estimation) - n_values))2428 )2429 cutoff = cutoff_scale * values.sum()24302431 # Applying the cutoff2432 weights[weights > cutoff] = cutoff24332434 y = weights / weights.sum()24352436 return y243724382439def convert_weights_and_uids_for_emit(2440 uids: np.ndarray, weights: np.ndarray2441) -> Tuple[List[int], List[int]]:2442 r"""Converts weights into integer u32 representation that sum to MAX_INT_WEIGHT.2443 Args:2444 uids (:obj:`np.ndarray,`):2445 Array of uids as destinations for passed weights.2446 weights (:obj:`np.ndarray,`):2447 Array of weights.2448 Returns:2449 weight_uids (List[int]):2450 Uids as a list.2451 weight_vals (List[int]):2452 Weights as a list.2453 """2454 # Checks.2455 uids = np.asarray(uids)2456 weights = np.asarray(weights)24572458 # Get non-zero weights and corresponding uids2459 non_zero_weights = weights[weights > 0]2460 non_zero_weight_uids = uids[weights > 0]24612462 # Debugging information2463 bittensor.logging.debug(f"weights: {weights}")2464 bittensor.logging.debug(f"non_zero_weights: {non_zero_weights}")2465 bittensor.logging.debug(f"uids: {uids}")2466 bittensor.logging.debug(f"non_zero_weight_uids: {non_zero_weight_uids}")24672468 if np.min(weights) < 0:2469 raise ValueError(2470 "Passed weight is negative cannot exist on chain {}".format(2471 weights2472 )2473 )2474 if np.min(uids) < 0:2475 raise ValueError(2476 "Passed uid is negative cannot exist on chain {}".format(uids)2477 )2478 if len(uids) != len(weights):2479 raise ValueError(2480 "Passed weights and uids must have the same length, got {} and {}".format(2481 len(uids), len(weights)2482 )2483 )2484 if np.sum(weights) == 0:2485 bittensor.logging.debug("nothing to set on chain")2486 return [], [] # Nothing to set on chain.2487 else:2488 max_weight = float(np.max(weights))2489 weights = [2490 float(value) / max_weight for value in weights2491 ] # max-upscale values (max_weight = 1).2492 bittensor.logging.debug(2493 f"setting on chain max: {max_weight} and weights: {weights}"2494 )24952496 weight_vals = []2497 weight_uids = []2498 for i, (weight_i, uid_i) in enumerate(list(zip(weights, uids))):2499 uint16_val = round(2500 float(weight_i) * int(U16_MAX)2501 ) # convert to int representation.25022503 # Filter zeros2504 if uint16_val != 0: # Filter zeros2505 weight_vals.append(uint16_val)2506 weight_uids.append(uid_i)2507 bittensor.logging.debug(f"final params: {weight_uids} : {weight_vals}")2508 return weight_uids, weight_vals250925102511def process_weights_for_netuid(2512 uids,2513 weights: np.ndarray,2514 netuid: int,2515 subtensor: "bittensor.subtensor",2516 metagraph: "bittensor.metagraph" = None,2517 exclude_quantile: int = 0,2518) -> Union[2519 tuple[2520 ndarray[Any, dtype[Any]],2521 Union[2522 Union[2523 ndarray[Any, dtype[floating[Any]]],2524 ndarray[Any, dtype[complexfloating[Any, Any]]],2525 ],2526 Any,2527 ],2528 ],2529 tuple[ndarray[Any, dtype[Any]], ndarray],2530 tuple[Any, ndarray],2531]:2532 bittensor.logging.debug("process_weights_for_netuid()")2533 bittensor.logging.debug("weights", weights)2534 bittensor.logging.debug("netuid", netuid)2535 bittensor.logging.debug("subtensor", subtensor)2536 bittensor.logging.debug("metagraph", metagraph)25372538 # Get latest metagraph from chain if metagraph is None.2539 if metagraph is None:2540 metagraph = subtensor.metagraph(netuid)25412542 # Cast weights to floats.2543 if not isinstance(weights, np.ndarray) or weights.dtype != np.float32:2544 weights = weights.astype(np.float32)25452546 # Network configuration parameters from an subtensor.2547 # These parameters determine the range of acceptable weights for each neuron.2548 quantile = exclude_quantile / U16_MAX2549 min_allowed_weights = subtensor.min_allowed_weights(netuid=netuid)2550 max_weight_limit = subtensor.max_weight_limit(netuid=netuid)2551 bittensor.logging.debug("quantile", quantile)2552 bittensor.logging.debug("min_allowed_weights", min_allowed_weights)2553 bittensor.logging.debug("max_weight_limit", max_weight_limit)25542555 # Find all non zero weights.2556 non_zero_weight_idx = np.argwhere(weights > 0).squeeze()2557 non_zero_weight_idx = np.atleast_1d(non_zero_weight_idx)2558 non_zero_weight_uids = uids[non_zero_weight_idx]2559 non_zero_weights = weights[non_zero_weight_idx]2560 if non_zero_weights.size == 0 or metagraph.n < min_allowed_weights:2561 bittensor.logging.warning("No non-zero weights returning all ones.")2562 final_weights = np.ones(metagraph.n) / metagraph.n2563 bittensor.logging.debug("final_weights", final_weights)2564 return np.arange(len(final_weights)), final_weights25652566 elif non_zero_weights.size < min_allowed_weights:2567 bittensor.logging.warning(2568 "No non-zero weights less then min allowed weight, returning all ones."2569 )2570 weights = (2571 np.ones(metagraph.n) * 1e-52572 ) # creating minimum even non-zero weights2573 weights[non_zero_weight_idx] += non_zero_weights2574 bittensor.logging.debug("final_weights", weights)2575 normalized_weights = normalize_max_weight(2576 x=weights, limit=max_weight_limit2577 )2578 return np.arange(len(normalized_weights)), normalized_weights25792580 bittensor.logging.debug("non_zero_weights", non_zero_weights)25812582 # Compute the exclude quantile and find the weights in the lowest quantile2583 max_exclude = max(0, len(non_zero_weights) - min_allowed_weights) / len(2584 non_zero_weights2585 )2586 exclude_quantile = min([quantile, max_exclude])2587 lowest_quantile = np.quantile(non_zero_weights, exclude_quantile)2588 bittensor.logging.debug("max_exclude", max_exclude)2589 bittensor.logging.debug("exclude_quantile", exclude_quantile)2590 bittensor.logging.debug("lowest_quantile", lowest_quantile)25912592 # Exclude all weights below the allowed quantile.2593 non_zero_weight_uids = non_zero_weight_uids[2594 lowest_quantile <= non_zero_weights2595 ]2596 non_zero_weights = non_zero_weights[lowest_quantile <= non_zero_weights]2597 bittensor.logging.debug("non_zero_weight_uids", non_zero_weight_uids)2598 bittensor.logging.debug("non_zero_weights", non_zero_weights)25992600 # Normalize weights and return.2601 normalized_weights = normalize_max_weight(2602 x=non_zero_weights, limit=max_weight_limit2603 )2604 bittensor.logging.debug("final_weights", normalized_weights)26052606 return non_zero_weight_uids, normalized_weights260726082609---2610target_repo/tests/__init__.py2611---261226132614---2615target_repo/tests/helpers.py2616---2617# The MIT License (MIT)2618# Copyright Β© 2023 Opentensor Foundation26192620# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated2621# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation2622# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,2623# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:26242625# The above copyright notice and this permission notice shall be included in all copies or substantial portions of2626# the Software.26272628# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO2629# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL2630# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION2631# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER2632# DEALINGS IN THE SOFTWARE.26332634from typing import Union2635from bittensor import (2636 Balance,2637 NeuronInfo,2638 AxonInfo,2639 PrometheusInfo,2640 __ss58_format__,2641)2642from bittensor.mock.wallet_mock import MockWallet as _MockWallet2643from bittensor.mock.wallet_mock import get_mock_coldkey as _get_mock_coldkey2644from bittensor.mock.wallet_mock import get_mock_hotkey as _get_mock_hotkey2645from bittensor.mock.wallet_mock import get_mock_keypair as _get_mock_keypair2646from bittensor.mock.wallet_mock import get_mock_wallet as _get_mock_wallet26472648from rich.console import Console2649from rich.text import Text265026512652def __mock_wallet_factory__(*args, **kwargs) -> _MockWallet:2653 """Returns a mock wallet object."""26542655 mock_wallet = _get_mock_wallet()26562657 return mock_wallet265826592660class CLOSE_IN_VALUE:2661 value: Union[float, int, Balance]2662 tolerance: Union[float, int, Balance]26632664 def __init__(2665 self,2666 value: Union[float, int, Balance],2667 tolerance: Union[float, int, Balance] = 0.0,2668 ) -> None:2669 self.value = value2670 self.tolerance = tolerance26712672 def __eq__(self, __o: Union[float, int, Balance]) -> bool:2673 # True if __o \in [value - tolerance, value + tolerance]2674 # or if value \in [__o - tolerance, __o + tolerance]2675 return (2676 (self.value - self.tolerance) <= __o2677 and __o <= (self.value + self.tolerance)2678 ) or (2679 (__o - self.tolerance) <= self.value2680 and self.value <= (__o + self.tolerance)2681 )268226832684def get_mock_neuron(**kwargs) -> NeuronInfo:2685 """2686 Returns a mock neuron with the given kwargs overriding the default values.2687 """26882689 mock_neuron_d = dict(2690 {2691 "netuid": -1, # mock netuid2692 "axon_info": AxonInfo(2693 block=0,2694 version=1,2695 ip=0,2696 port=0,2697 ip_type=0,2698 protocol=0,2699 placeholder1=0,2700 placeholder2=0,2701 ),2702 "prometheus_info": PrometheusInfo(2703 block=0, version=1, ip=0, port=0, ip_type=02704 ),2705 "validator_permit": True,2706 "uid": 1,2707 "hotkey": "some_hotkey",2708 "coldkey": "some_coldkey",2709 "active": 0,2710 "last_update": 0,2711 "stake": {"some_coldkey": 1e12},2712 "total_stake": 1e12,2713 "rank": 0.0,2714 "trust": 0.0,2715 "consensus": 0.0,2716 "validator_trust": 0.0,2717 "incentive": 0.0,2718 "dividends": 0.0,2719 "emission": 0.0,2720 "bonds": [],2721 "weights": [],2722 "stake_dict": {},2723 "pruning_score": 0.0,2724 "is_null": False,2725 }2726 )27272728 mock_neuron_d.update(kwargs) # update with kwargs27292730 if kwargs.get("stake") is None and kwargs.get("coldkey") is not None:2731 mock_neuron_d["stake"] = {kwargs.get("coldkey"): 1e12}27322733 if kwargs.get("total_stake") is None:2734 mock_neuron_d["total_stake"] = sum(mock_neuron_d["stake"].values())27352736 mock_neuron = NeuronInfo._neuron_dict_to_namespace(mock_neuron_d)27372738 return mock_neuron273927402741def get_mock_neuron_by_uid(uid: int, **kwargs) -> NeuronInfo:2742 return get_mock_neuron(2743 uid=uid,2744 hotkey=_get_mock_hotkey(uid),2745 coldkey=_get_mock_coldkey(uid),2746 **kwargs2747 )274827492750class MockStatus:2751 def __enter__(self):2752 return self27532754 def __exit__(self, exc_type, exc_value, traceback):2755 pass27562757 def start(self):2758 pass27592760 def stop(self):2761 pass27622763 def update(self, *args, **kwargs):2764 MockConsole().print(*args, **kwargs)276527662767class MockConsole:2768 """2769 Mocks the console object for status and print.2770 Captures the last print output as a string.2771 """27722773 captured_print = None27742775 def status(self, *args, **kwargs):2776 return MockStatus()27772778 def print(self, *args, **kwargs):2779 console = Console(2780 width=1000, no_color=True, markup=False2781 ) # set width to 1000 to avoid truncation2782 console.begin_capture()2783 console.print(*args, **kwargs)2784 self.captured_print = console.end_capture()27852786 def clear(self, *args, **kwargs):2787 pass27882789 @staticmethod2790 def remove_rich_syntax(text: str) -> str:2791 """2792 Removes rich syntax from the given text.2793 Removes markup and ansi syntax.2794 """2795 output_no_syntax = Text.from_ansi(Text.from_markup(text).plain).plain27962797 return output_no_syntax279827992800---2801target_repo/tests/test_mock.py2802---2803import pytest2804import asyncio2805import bittensor as bt2806from prompting.mock import MockDendrite, MockMetagraph, MockSubtensor2807from prompting.protocol import PromptingSynapse280828092810@pytest.mark.parametrize("netuid", [1, 2, 3])2811@pytest.mark.parametrize("n", [2, 4, 8, 16, 32, 64])2812@pytest.mark.parametrize("wallet", [bt.MockWallet(), None])2813def test_mock_subtensor(netuid, n, wallet):2814 subtensor = MockSubtensor(netuid=netuid, n=n, wallet=wallet)2815 neurons = subtensor.neurons(netuid=netuid)2816 # Check netuid2817 assert subtensor.subnet_exists(netuid)2818 # Check network2819 assert subtensor.network == "mock"2820 assert subtensor.chain_endpoint == "mock_endpoint"2821 # Check number of neurons2822 assert len(neurons) == (n + 1 if wallet is not None else n)2823 # Check wallet2824 if wallet is not None:2825 assert subtensor.is_hotkey_registered(2826 netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address2827 )28282829 for neuron in neurons:2830 assert type(neuron) == bt.NeuronInfo2831 assert subtensor.is_hotkey_registered(2832 netuid=netuid, hotkey_ss58=neuron.hotkey2833 )283428352836@pytest.mark.parametrize("n", [16, 32, 64])2837def test_mock_metagraph(n):2838 mock_subtensor = MockSubtensor(netuid=1, n=n)2839 mock_metagraph = MockMetagraph(subtensor=mock_subtensor)2840 # Check axons2841 axons = mock_metagraph.axons2842 assert len(axons) == n2843 # Check ip and port2844 for axon in axons:2845 assert type(axon) == bt.AxonInfo2846 assert axon.ip == mock_metagraph.default_ip2847 assert axon.port == mock_metagraph.default_port284828492850def test_mock_reward_pipeline():2851 pass285228532854def test_mock_neuron():2855 pass285628572858@pytest.mark.parametrize("timeout", [0.1, 0.2])2859@pytest.mark.parametrize("min_time", [0, 0.05, 0.1])2860@pytest.mark.parametrize("max_time", [0.1, 0.15, 0.2])2861@pytest.mark.parametrize("n", [4, 16, 64])2862def test_mock_dendrite_timings(timeout, min_time, max_time, n):2863 mock_wallet = None2864 mock_dendrite = MockDendrite(mock_wallet)2865 mock_dendrite.min_time = min_time2866 mock_dendrite.max_time = max_time2867 mock_subtensor = MockSubtensor(netuid=1, n=n)2868 mock_metagraph = MockMetagraph(subtensor=mock_subtensor)2869 axons = mock_metagraph.axons28702871 async def run():2872 return await mock_dendrite(2873 axons,2874 synapse=PromptingSynapse(2875 roles=["user"], messages=["What is the capital of France?"]2876 ),2877 timeout=timeout,2878 )28792880 responses = asyncio.run(run())2881 for synapse in responses:2882 assert (2883 hasattr(synapse, "dendrite")2884 and type(synapse.dendrite) == bt.TerminalInfo2885 )28862887 dendrite = synapse.dendrite2888 # check synapse.dendrite has (process_time, status_code, status_message)2889 for field in ("process_time", "status_code", "status_message"):2890 assert (2891 hasattr(dendrite, field)2892 and getattr(dendrite, field) is not None2893 )28942895 # check that the dendrite take between min_time and max_time2896 assert min_time <= dendrite.process_time2897 assert dendrite.process_time <= max_time + 0.12898 # check that responses which take longer than timeout have 408 status code2899 if dendrite.process_time >= timeout + 0.1:2900 assert dendrite.status_code == 4082901 assert dendrite.status_message == "Timeout"2902 assert synapse.dummy_output == synapse.dummy_input2903 # check that responses which take less than timeout have 200 status code2904 elif dendrite.process_time < timeout:2905 assert dendrite.status_code == 2002906 assert dendrite.status_message == "OK"2907 # check that outputs are not empty for successful responses2908 assert synapse.dummy_output == synapse.dummy_input * 22909 # dont check for responses which take between timeout and max_time because they are not guaranteed to have a status code of 200 or 408291029112912---2913target_repo/tests/test_sybil_validator.py2914---2915# The MIT License (MIT)2916# Copyright Β© 2023 Yuma Rao2917# Copyright Β© 2023 Opentensor Foundation29182919# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated2920# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation2921# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,2922# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:29232924# The above copyright notice and this permission notice shall be included in all copies or substantial portions of2925# the Software.29262927# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO2928# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL2929# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION2930# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER2931# DEALINGS IN THE SOFTWARE.29322933import sys2934import unittest29352936import bittensor as bt2937import torch29382939from neurons.validator import Validator2940from sybil.base.validator import BaseValidatorNeuron2941from sybil.protocol import Dummy2942from sybil.utils.uids import get_random_uids2943from sybil.validator.reward import get_rewards294429452946class SybilValidatorNeuronTestCase(unittest.TestCase):2947 """2948 This class contains unit tests for the RewardEvent classes.29492950 The tests cover different scenarios where completions may or may not be successful and the reward events are checked that they don't contain missing values.2951 The `reward` attribute of all RewardEvents is expected to be a float, and the `is_filter_model` attribute is expected to be a boolean.2952 """29532954 def setUp(self):2955 sys.argv = sys.argv[0] + ["--config", "tests/configs/validator.json"]29562957 config = BaseValidatorNeuron.config()2958 config.wallet._mock = True2959 config.metagraph._mock = True2960 config.subtensor._mock = True2961 self.neuron = Validator(config)2962 self.miner_uids = get_random_uids(self, k=10)29632964 def test_run_single_step(self):2965 # TODO: Test a single step2966 pass29672968 def test_sync_error_if_not_registered(self):2969 # TODO: Test that the validator throws an error if it is not registered on metagraph2970 pass29712972 def test_forward(self):2973 # TODO: Test that the forward function returns the correct value2974 pass29752976 def test_dummy_responses(self):2977 # TODO: Test that the dummy responses are correctly constructed29782979 responses = self.neuron.dendrite.query(2980 # Send the query to miners in the network.2981 axons=[2982 self.neuron.metagraph.axons[uid] for uid in self.miner_uids2983 ],2984 # Construct a dummy query.2985 synapse=Dummy(dummy_input=self.neuron.step),2986 # All responses have the deserialize function called on them before returning.2987 deserialize=True,2988 )29892990 for i, response in enumerate(responses):2991 self.assertEqual(response, self.neuron.step * 2)29922993 def test_reward(self):2994 # TODO: Test that the reward function returns the correct value2995 responses = self.dendrite.query(2996 # Send the query to miners in the network.2997 axons=[self.metagraph.axons[uid] for uid in self.miner_uids],2998 # Construct a dummy query.2999 synapse=Dummy(dummy_input=self.neuron.step),3000 # All responses have the deserialize function called on them before returning.3001 deserialize=True,3002 )30033004 rewards = get_rewards(self.neuron, responses)3005 expected_rewards = torch.FloatTensor([1.0] * len(responses))3006 self.assertEqual(rewards, expected_rewards)30073008 def test_reward_with_nan(self):3009 # TODO: Test that NaN rewards are correctly sanitized3010 # TODO: Test that a bt.logging.warning is thrown when a NaN reward is sanitized3011 responses = self.dendrite.query(3012 # Send the query to miners in the network.3013 axons=[self.metagraph.axons[uid] for uid in self.miner_uids],3014 # Construct a dummy query.3015 synapse=Dummy(dummy_input=self.neuron.step),3016 # All responses have the deserialize function called on them before returning.3017 deserialize=True,3018 )30193020 rewards = get_rewards(self.neuron, responses)3021 expected_rewards = rewards.clone()3022 # Add NaN values to rewards3023 rewards[0] = float("nan")30243025 with self.assertLogs(bt.logging, level="WARNING") as cm:3026 self.neuron.update_scores(rewards, self.miner_uids)302730283029---3030target_repo/docs/stream_tutorial/client.py3031---3032import argparse3033import asyncio3034import bittensor as bt30353036from protocol import StreamPrompting30373038"""3039This has assumed you have:30401. Registered your miner on the chain (finney/test)30412. Are serving your miner on an open port (e.g. 12345)30423043Steps:3044- Instantiate your synapse subclass with the relevant information. E.g. messages, roles, etc.3045- Instantiate your wallet and a dendrite client3046- Query the dendrite client with your synapse object3047- Iterate over the async generator to extract the yielded tokens on the server side3048"""304930503051async def query_synapse(my_uid, wallet_name, hotkey, network, netuid):3052 syn = StreamPrompting(3053 roles=["user"],3054 messages=[3055 "hello this is a test of a streaming response. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."3056 ],3057 )30583059 # create a wallet instance with provided wallet name and hotkey3060 wallet = bt.wallet(name=wallet_name, hotkey=hotkey)30613062 # instantiate the metagraph with provided network and netuid3063 metagraph = bt.metagraph(3064 netuid=netuid, network=network, sync=True, lite=False3065 )30663067 # Grab the axon you're serving3068 axon = metagraph.axons[my_uid]30693070 # Create a Dendrite instance to handle client-side communication.3071 dendrite = bt.dendrite(wallet=wallet)30723073 async def main():3074 responses = await dendrite(3075 [axon], syn, deserialize=False, streaming=True3076 )30773078 for resp in responses:3079 i = 03080 async for chunk in resp:3081 i += 13082 if i % 5 == 0:3083 print()3084 if isinstance(chunk, list):3085 print(chunk[0], end="", flush=True)3086 else:3087 # last object yielded is the synapse itself with completion filled3088 synapse = chunk3089 break30903091 # Run the main function with asyncio3092 await main()309330943095if __name__ == "__main__":3096 parser = argparse.ArgumentParser(3097 description="Query a Bittensor synapse with given parameters."3098 )30993100 # Adding arguments3101 parser.add_argument(3102 "--my_uid",3103 type=int,3104 required=True,3105 help="Your unique miner ID on the chain",3106 )3107 parser.add_argument(3108 "--netuid", type=int, required=True, help="Network Unique ID"3109 )3110 parser.add_argument(3111 "--wallet_name", type=str, default="default", help="Name of the wallet"3112 )3113 parser.add_argument(3114 "--hotkey", type=str, default="default", help="Hotkey for the wallet"3115 )3116 parser.add_argument(3117 "--network",3118 type=str,3119 default="test",3120 help='Network type, e.g., "test" or "mainnet"',3121 )31223123 # Parse arguments3124 args = parser.parse_args()31253126 # Running the async function with provided arguments3127 asyncio.run(3128 query_synapse(3129 args.my_uid,3130 args.wallet_name,3131 args.hotkey,3132 args.network,3133 args.netuid,3134 )3135 )313631373138---3139target_repo/docs/stream_tutorial/config.py3140---3141import bittensor as bt3142import argparse3143import os314431453146def check_config(cls, config: "bt.Config"):3147 bt.axon.check_config(config)3148 bt.logging.check_config(config)3149 full_path = os.path.expanduser(3150 "{}/{}/{}/{}".format(3151 config.logging.logging_dir,3152 config.wallet.get("name", bt.defaults.wallet.name),3153 config.wallet.get("hotkey", bt.defaults.wallet.hotkey),3154 config.miner.name,3155 )3156 )3157 config.miner.full_path = os.path.expanduser(full_path)3158 if not os.path.exists(config.miner.full_path):3159 os.makedirs(config.miner.full_path)316031613162def get_config() -> "bt.Config":3163 parser = argparse.ArgumentParser()3164 parser.add_argument(3165 "--axon.port", type=int, default=8098, help="Port to run the axon on."3166 )3167 # Subtensor network to connect to3168 parser.add_argument(3169 "--subtensor.network",3170 default="finney",3171 help="Bittensor network to connect to.",3172 )3173 # Chain endpoint to connect to3174 parser.add_argument(3175 "--subtensor.chain_endpoint",3176 default="wss://entrypoint-finney.opentensor.ai:443",3177 help="Chain endpoint to connect to.",3178 )3179 # Adds override arguments for network and netuid.3180 parser.add_argument(3181 "--netuid", type=int, default=1, help="The chain subnet uid."3182 )31833184 parser.add_argument(3185 "--miner.root",3186 type=str,3187 help="Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name ",3188 default="~/.bittensor/miners/",3189 )3190 parser.add_argument(3191 "--miner.name",3192 type=str,3193 help="Trials for this miner go in miner.root / (wallet_cold - wallet_hot) / miner.name ",3194 default="Bittensor Miner",3195 )31963197 # Run config.3198 parser.add_argument(3199 "--miner.blocks_per_epoch",3200 type=str,3201 help="Blocks until the miner repulls the metagraph from the chain",3202 default=100,3203 )32043205 # Switches.3206 parser.add_argument(3207 "--miner.no_serve",3208 action="store_true",3209 help="If True, the miner doesnt serve the axon.",3210 default=False,3211 )3212 parser.add_argument(3213 "--miner.no_start_axon",3214 action="store_true",3215 help="If True, the miner doesnt start the axon.",3216 default=False,3217 )32183219 # Mocks.3220 parser.add_argument(3221 "--miner.mock_subtensor",3222 action="store_true",3223 help="If True, the miner will allow non-registered hotkeys to mine.",3224 default=False,3225 )32263227 # Adds subtensor specific arguments i.e. --subtensor.chain_endpoint ... --subtensor.network ...3228 bt.subtensor.add_args(parser)32293230 # Adds logging specific arguments i.e. --logging.debug ..., --logging.trace .. or --logging.logging_dir ...3231 bt.logging.add_args(parser)32323233 # Adds wallet specific arguments i.e. --wallet.name ..., --wallet.hotkey ./. or --wallet.path ...3234 bt.wallet.add_args(parser)32353236 # Adds axon specific arguments i.e. --axon.port ...3237 bt.axon.add_args(parser)32383239 # Activating the parser to read any command-line inputs.3240 # To print help message, run python3 template/miner.py --help3241 config = bt.config(parser)32423243 # Logging captures events for diagnosis or understanding miner's behavior.3244 config.full_path = os.path.expanduser(3245 "{}/{}/{}/netuid{}/{}".format(3246 config.logging.logging_dir,3247 config.wallet.name,3248 config.wallet.hotkey,3249 config.netuid,3250 "miner",3251 )3252 )3253 # Ensure the directory for logging exists, else create one.3254 if not os.path.exists(config.full_path):3255 os.makedirs(config.full_path, exist_ok=True)3256 return config325732583259---3260target_repo/docs/stream_tutorial/miner.py3261---3262import copy3263import time3264import asyncio3265import argparse3266import threading3267import traceback3268from abc import ABC, abstractmethod3269from functools import partial3270from starlette.types import Send32713272import bittensor as bt3273from transformers import GPT2Tokenizer3274from typing import List, Dict, Tuple, Union, Callable, Awaitable32753276from protocol import StreamPrompting3277from config import get_config, check_config327832793280class StreamMiner(ABC):3281 def __init__(self, config=None, axon=None, wallet=None, subtensor=None):3282 # Setup base config from Miner.config() and merge with subclassed config.3283 base_config = copy.deepcopy(config or get_config())3284 self.config = self.config()3285 self.config.merge(base_config)32863287 check_config(StreamMiner, self.config)3288 bt.logging.info(self.config) # TODO: duplicate print?32893290 self.prompt_cache: Dict[str, Tuple[str, int]] = {}32913292 # Activating Bittensor's logging with the set configurations.3293 bt.logging.set_config(config=self.config.logging)32943295 # Wallet holds cryptographic information, ensuring secure transactions and communication.3296 self.wallet = wallet or bt.wallet(config=self.config)3297 bt.logging.info(f"Wallet {self.wallet}")32983299 # subtensor manages the blockchain connection, facilitating interaction with the Bittensor blockchain.3300 self.subtensor = subtensor or bt.subtensor(config=self.config)3301 bt.logging.info(f"Subtensor: {self.subtensor}")3302 bt.logging.info(3303 f"Running miner for subnet: {self.config.netuid} on network: {self.subtensor.chain_endpoint} with config:"3304 )33053306 # metagraph provides the network's current state, holding state about other participants in a subnet.3307 self.metagraph = self.subtensor.metagraph(self.config.netuid)3308 bt.logging.info(f"Metagraph: {self.metagraph}")33093310 if self.wallet.hotkey.ss58_address not in self.metagraph.hotkeys:3311 bt.logging.error(3312 f"\nYour validator: {self.wallet} if not registered to chain connection: {self.subtensor} \nRun btcli register and try again. "3313 )3314 exit()3315 else:3316 # Each miner gets a unique identity (UID) in the network for differentiation.3317 self.my_subnet_uid = self.metagraph.hotkeys.index(3318 self.wallet.hotkey.ss58_address3319 )3320 bt.logging.info(f"Running miner on uid: {self.my_subnet_uid}")33213322 # The axon handles request processing, allowing validators to send this process requests.3323 self.axon = axon or bt.axon(3324 wallet=self.wallet, port=self.config.axon.port3325 )3326 # Attach determiners which functions are called when servicing a request.3327 bt.logging.info(f"Attaching forward function to axon.")3328 print(f"Attaching forward function to axon. {self._prompt}")3329 self.axon.attach(3330 forward_fn=self._prompt,3331 )3332 bt.logging.info(f"Axon created: {self.axon}")33333334 # Instantiate runners3335 self.should_exit: bool = False3336 self.is_running: bool = False3337 self.thread: threading.Thread = None3338 self.lock = asyncio.Lock()3339 self.request_timestamps: Dict = {}33403341 @abstractmethod3342 def config(self) -> "bt.Config":3343 ...33443345 @classmethod3346 @abstractmethod3347 def add_args(cls, parser: argparse.ArgumentParser):3348 ...33493350 def _prompt(self, synapse: StreamPrompting) -> StreamPrompting:3351 """3352 A wrapper method around the `prompt` method that will be defined by the subclass.33533354 This method acts as an intermediary layer to perform pre-processing before calling the3355 actual `prompt` method implemented in the subclass. Specifically, it checks whether a3356 prompt is in cache to avoid reprocessing recent requests. If the prompt is not in the3357 cache, the subclass `prompt` method is called.33583359 Args:3360 synapse (StreamPrompting): The incoming request object encapsulating the details of the request.33613362 Returns:3363 StreamPrompting: The response object to be sent back in reply to the incoming request, essentially3364 the filled synapse request object.33653366 Raises:3367 ValueError: If the prompt is found in the cache indicating it was sent recently.33683369 Example:3370 This method is not meant to be called directly but is invoked internally when a request3371 is received, and it subsequently calls the `prompt` method of the subclass.3372 """3373 return self.prompt(synapse)33743375 @abstractmethod3376 def prompt(self, synapse: StreamPrompting) -> StreamPrompting:3377 """3378 Abstract method to handle and respond to incoming requests to the miner.33793380 Subclasses should implement this method to define their custom logic for processing and3381 responding to requests. This method is designed to be overridden, and its behavior will3382 be dependent on the specific implementation provided in the subclass.33833384 Args:3385 synapse (StreamPrompting): The incoming request object encapsulating the details3386 of the request. This must contain `messages` and `roles` as fields.33873388 Returns:3389 StreamPrompting: The response object that should be sent back in reply to the3390 incoming request. This is essentially the filled synapse request object.33913392 Example:3393 class CustomMiner(Miner):3394 def prompt(self, synapse: StreamPrompting) -> StreamPrompting:3395 # Custom logic to process and respond to the request.3396 synapse.completion = "The meaning of life is 42."3397 return synapse3398 """3399 ...34003401 def run(self):3402 """3403 Runs the miner logic. This method starts the miner's operations, including3404 listening for incoming requests and periodically updating the miner's knowledge3405 of the network graph.3406 """3407 if not self.subtensor.is_hotkey_registered(3408 netuid=self.config.netuid,3409 hotkey_ss58=self.wallet.hotkey.ss58_address,3410 ):3411 bt.logging.error(3412 f"Wallet: {self.wallet} is not registered on netuid {self.config.netuid}"3413 f"Please register the hotkey using `btcli subnets register` before trying again"3414 )3415 exit()34163417 # Serve passes the axon information to the network + netuid we are hosting on.3418 # This will auto-update if the axon port of external ip have changed.3419 bt.logging.info(3420 f"Serving axon {StreamPrompting} on network: {self.config.subtensor.chain_endpoint} with netuid: {self.config.netuid}"3421 )3422 self.axon.serve(netuid=self.config.netuid, subtensor=self.subtensor)34233424 # Start starts the miner's axon, making it active on the network.3425 bt.logging.info(3426 f"Starting axon server on port: {self.config.axon.port}"3427 )3428 self.axon.start()34293430 # --- Run until should_exit = True.3431 self.last_epoch_block = self.subtensor.get_current_block()3432 bt.logging.info(f"Miner starting at block: {self.last_epoch_block}")34333434 # This loop maintains the miner's operations until intentionally stopped.3435 bt.logging.info(f"Starting main loop")3436 step = 03437 try:3438 while not self.should_exit:3439 start_epoch = time.time()34403441 # --- Wait until next epoch.3442 current_block = self.subtensor.get_current_block()3443 while (3444 current_block - self.last_epoch_block3445 < self.config.miner.blocks_per_epoch3446 ):3447 # --- Wait for next bloc.3448 time.sleep(1)3449 current_block = self.subtensor.get_current_block()34503451 # --- Check if we should exit.3452 if self.should_exit:3453 break34543455 # --- Update the metagraph with the latest network state.3456 self.last_epoch_block = self.subtensor.get_current_block()34573458 metagraph = self.subtensor.metagraph(3459 netuid=self.config.netuid,3460 lite=True,3461 block=self.last_epoch_block,3462 )3463 log = (3464 f"Step:{step} | "3465 f"Block:{metagraph.block.item()} | "3466 f"Stake:{metagraph.S[self.my_subnet_uid]} | "3467 f"Rank:{metagraph.R[self.my_subnet_uid]} | "3468 f"Trust:{metagraph.T[self.my_subnet_uid]} | "3469 f"Consensus:{metagraph.C[self.my_subnet_uid] } | "3470 f"Incentive:{metagraph.I[self.my_subnet_uid]} | "3471 f"Emission:{metagraph.E[self.my_subnet_uid]}"3472 )3473 bt.logging.info(log)34743475 step += 134763477 # If someone intentionally stops the miner, it'll safely terminate operations.3478 except KeyboardInterrupt:3479 self.axon.stop()3480 bt.logging.success("Miner killed by keyboard interrupt.")3481 exit()34823483 # In case of unforeseen errors, the miner will log the error and continue operations.3484 except Exception as e:3485 bt.logging.error(traceback.format_exc())34863487 def run_in_background_thread(self):3488 """3489 Starts the miner's operations in a separate background thread.3490 This is useful for non-blocking operations.3491 """3492 if not self.is_running:3493 bt.logging.debug("Starting miner in background thread.")3494 self.should_exit = False3495 self.thread = threading.Thread(target=self.run, daemon=True)3496 self.thread.start()3497 self.is_running = True3498 bt.logging.debug("Started")34993500 def stop_run_thread(self):3501 """3502 Stops the miner's operations that are running in the background thread.3503 """3504 if self.is_running:3505 bt.logging.debug("Stopping miner in background thread.")3506 self.should_exit = True3507 self.thread.join(5)3508 self.is_running = False3509 bt.logging.debug("Stopped")35103511 def __enter__(self):3512 """3513 Starts the miner's operations in a background thread upon entering the context.3514 This method facilitates the use of the miner in a 'with' statement.3515 """3516 self.run_in_background_thread()35173518 def __exit__(self, exc_type, exc_value, traceback):3519 """3520 Stops the miner's background operations upon exiting the context.3521 This method facilitates the use of the miner in a 'with' statement.35223523 Args:3524 exc_type: The type of the exception that caused the context to be exited.3525 None if the context was exited without an exception.3526 exc_value: The instance of the exception that caused the context to be exited.3527 None if the context was exited without an exception.3528 traceback: A traceback object encoding the stack trace.3529 None if the context was exited without an exception.3530 """3531 self.stop_run_thread()353235333534class StreamingTemplateMiner(StreamMiner):3535 def config(self) -> "bt.Config":3536 """3537 Returns the configuration object specific to this miner.35383539 Implement and extend this method to provide custom configurations for the miner.3540 Currently, it sets up a basic configuration parser.35413542 Returns:3543 bt.Config: A configuration object with the miner's operational parameters.3544 """3545 parser = argparse.ArgumentParser(description="Streaming Miner Configs")3546 self.add_args(parser)3547 return bt.config(parser)35483549 def add_args(cls, parser: argparse.ArgumentParser):3550 """3551 Adds custom arguments to the command line parser.35523553 Developers can introduce additional command-line arguments specific to the miner's3554 functionality in this method. These arguments can then be used to configure the miner's operation.35553556 Args:3557 parser (argparse.ArgumentParser):3558 The command line argument parser to which custom arguments should be added.3559 """3560 pass35613562 def prompt(self, synapse: StreamPrompting) -> StreamPrompting:3563 """3564 Generates a streaming response for the provided synapse.35653566 This function serves as the main entry point for handling streaming prompts. It takes3567 the incoming synapse which contains messages to be processed and returns a streaming3568 response. The function uses the GPT-2 tokenizer and a simulated model to tokenize and decode3569 the incoming message, and then sends the response back to the client token by token.35703571 Args:3572 synapse (StreamPrompting): The incoming StreamPrompting instance containing the messages to be processed.35733574 Returns:3575 StreamPrompting: The streaming response object which can be used by other functions to3576 stream back the response to the client.35773578 Usage:3579 This function can be extended and customized based on specific requirements of the3580 miner. Developers can swap out the tokenizer, model, or adjust how streaming responses3581 are generated to suit their specific applications.3582 """3583 bt.logging.trace("HI. PROMPT()")3584 tokenizer = GPT2Tokenizer.from_pretrained("gpt2")35853586 # Simulated function to decode token IDs into strings. In a real-world scenario,3587 # this can be replaced with an actual model inference step.3588 def model(ids):3589 return (tokenizer.decode(id) for id in ids)35903591 async def _prompt(text: str, send: Send):3592 """3593 Asynchronously processes the input text and sends back tokens as a streaming response.35943595 This function takes an input text, tokenizes it using the GPT-2 tokenizer, and then3596 uses the simulated model to decode token IDs into strings. It then sends each token3597 back to the client as a streaming response, with a delay between tokens to simulate3598 the effect of real-time streaming.35993600 Args:3601 text (str): The input text message to be processed.3602 send (Send): An asynchronous function that allows sending back the streaming response.36033604 Usage:3605 This function can be adjusted based on the streaming requirements, speed of3606 response, or the model being used. Developers can also introduce more sophisticated3607 processing steps or modify how tokens are sent back to the client.3608 """3609 bt.logging.trace("HI. _PROMPT()")3610 input_ids = tokenizer(3611 text, return_tensors="pt"3612 ).input_ids.squeeze()3613 buffer = []3614 bt.logging.debug(f"Input text: {text}")3615 bt.logging.debug(f"Input ids: {input_ids}")36163617 N = 3 # Number of tokens to send back to the client at a time3618 for token in model(input_ids):3619 bt.logging.trace(f"appending token: {token}")3620 buffer.append(token)3621 # If buffer has N tokens, send them back to the client.3622 if len(buffer) == N:3623 time.sleep(0.1)3624 joined_buffer = "".join(buffer)3625 bt.logging.debug(f"sedning tokens: {joined_buffer}")3626 await send(3627 {3628 "type": "http.response.body",3629 "body": joined_buffer.encode("utf-8"),3630 "more_body": True,3631 }3632 )3633 bt.logging.debug(f"Streamed tokens: {joined_buffer}")3634 buffer = [] # Clear the buffer for next batch of tokens36353636 # Send any remaining tokens in the buffer3637 if buffer:3638 joined_buffer = "".join(buffer)3639 await send(3640 {3641 "type": "http.response.body",3642 "body": joined_buffer.encode("utf-8"),3643 "more_body": False, # No more tokens to send3644 }3645 )3646 bt.logging.trace(f"Streamed tokens: {joined_buffer}")36473648 message = synapse.messages[0]3649 bt.logging.trace(f"message in _prompt: {message}")3650 token_streamer = partial(_prompt, message)3651 bt.logging.trace(f"token streamer: {token_streamer}")3652 return synapse.create_streaming_response(token_streamer)365336543655# This is the main function, which runs the miner.3656if __name__ == "__main__":3657 with StreamingTemplateMiner():3658 while True:3659 time.sleep(1)366036613662---3663target_repo/docs/stream_tutorial/protocol.py3664---3665import pydantic3666import bittensor as bt36673668from abc import ABC, abstractmethod3669from typing import List, Union, Callable, Awaitable3670from starlette.responses import StreamingResponse367136723673class StreamPrompting(bt.StreamingSynapse):3674 """3675 StreamPrompting is a specialized implementation of the `StreamingSynapse` tailored for prompting functionalities within3676 the Bittensor network. This class is intended to interact with a streaming response that contains a sequence of tokens,3677 which represent prompts or messages in a certain scenario.36783679 As a developer, when using or extending the `StreamPrompting` class, you should be primarily focused on the structure3680 and behavior of the prompts you are working with. The class has been designed to seamlessly handle the streaming,3681 decoding, and accumulation of tokens that represent these prompts.36823683 Attributes:3684 - `roles` (List[str]): A list of roles involved in the prompting scenario. This could represent different entities3685 or agents involved in the conversation or use-case. They are immutable to ensure consistent3686 interaction throughout the lifetime of the object.36873688 - `messages` (List[str]): These represent the actual prompts or messages in the prompting scenario. They are also3689 immutable to ensure consistent behavior during processing.36903691 - `completion` (str): Stores the processed result of the streaming tokens. As tokens are streamed, decoded, and3692 processed, they are accumulated in the completion attribute. This represents the "final"3693 product or result of the streaming process.3694 - `required_hash_fields` (List[str]): A list of fields that are required for the hash.36953696 Methods:3697 - `process_streaming_response`: This method asynchronously processes the incoming streaming response by decoding3698 the tokens and accumulating them in the `completion` attribute.36993700 - `deserialize`: Converts the `completion` attribute into its desired data format, in this case, a string.37013702 - `extract_response_json`: Extracts relevant JSON data from the response, useful for gaining insights on the response's3703 metadata or for debugging purposes.37043705 Note: While you can directly use the `StreamPrompting` class, it's designed to be extensible. Thus, you can create3706 subclasses to further customize behavior for specific prompting scenarios or requirements.3707 """37083709 roles: List[str] = pydantic.Field(3710 ...,3711 title="Roles",3712 description="A list of roles in the StreamPrompting scenario. Immuatable.",3713 allow_mutation=False,3714 )37153716 messages: List[str] = pydantic.Field(3717 ...,3718 title="Messages",3719 description="A list of messages in the StreamPrompting scenario. Immutable.",3720 allow_mutation=False,3721 )37223723 required_hash_fields: List[str] = pydantic.Field(3724 ["messages"],3725 title="Required Hash Fields",3726 description="A list of required fields for the hash.",3727 allow_mutation=False,3728 )37293730 completion: str = pydantic.Field(3731 "",3732 title="Completion",3733 description="Completion status of the current StreamPrompting object. This attribute is mutable and can be updated.",3734 )37353736 async def process_streaming_response(self, response: StreamingResponse):3737 """3738 `process_streaming_response` is an asynchronous method designed to process the incoming streaming response from the3739 Bittensor network. It's the heart of the StreamPrompting class, ensuring that streaming tokens, which represent3740 prompts or messages, are decoded and appropriately managed.37413742 As the streaming response is consumed, the tokens are decoded from their 'utf-8' encoded format, split based on3743 newline characters, and concatenated into the `completion` attribute. This accumulation of decoded tokens in the3744 `completion` attribute allows for a continuous and coherent accumulation of the streaming content.37453746 Args:3747 response: The streaming response object containing the content chunks to be processed. Each chunk in this3748 response is expected to be a set of tokens that can be decoded and split into individual messages or prompts.3749 """3750 if self.completion is None:3751 self.completion = ""3752 bt.logging.debug(3753 "Processing streaming response (StreamingSynapse base class)."3754 )3755 async for chunk in response.content.iter_any():3756 bt.logging.debug(f"Processing chunk: {chunk}")3757 tokens = chunk.decode("utf-8").split("\n")3758 for token in tokens:3759 bt.logging.debug(f"--processing token: {token}")3760 if token:3761 self.completion += token3762 bt.logging.debug(f"yielding tokens {tokens}")3763 yield tokens37643765 def deserialize(self) -> str:3766 """3767 Deserializes the response by returning the completion attribute.37683769 Returns:3770 str: The completion result.3771 """3772 return self.completion37733774 def extract_response_json(self, response: StreamingResponse) -> dict:3775 """3776 `extract_response_json` is a method that performs the crucial task of extracting pertinent JSON data from the given3777 response. The method is especially useful when you need a detailed insight into the streaming response's metadata3778 or when debugging response-related issues.37793780 Beyond just extracting the JSON data, the method also processes and structures the data for easier consumption3781 and understanding. For instance, it extracts specific headers related to dendrite and axon, offering insights3782 about the Bittensor network's internal processes. The method ultimately returns a dictionary with a structured3783 view of the extracted data.37843785 Args:3786 response: The response object from which to extract the JSON data. This object typically includes headers and3787 content which can be used to glean insights about the response.37883789 Returns:3790 dict: A structured dictionary containing:3791 - Basic response metadata such as name, timeout, total_size, and header_size.3792 - Dendrite and Axon related information extracted from headers.3793 - Roles and Messages pertaining to the current StreamPrompting instance.3794 - The accumulated completion.3795 """3796 headers = {3797 k.decode("utf-8"): v.decode("utf-8")3798 for k, v in response.__dict__["_raw_headers"]3799 }38003801 def extract_info(prefix):3802 return {3803 key.split("_")[-1]: value3804 for key, value in headers.items()3805 if key.startswith(prefix)3806 }38073808 return {3809 "name": headers.get("name", ""),3810 "timeout": float(headers.get("timeout", 0)),3811 "total_size": int(headers.get("total_size", 0)),3812 "header_size": int(headers.get("header_size", 0)),3813 "dendrite": extract_info("bt_header_dendrite"),3814 "axon": extract_info("bt_header_axon"),3815 "roles": self.roles,3816 "messages": self.messages,3817 "completion": self.completion,3818 }381938203821---3822target_repo/neurons/__init__.py3823---382438253826---3827target_repo/neurons/miner.py3828---3829# The MIT License (MIT)3830# Copyright Β© 2023 Yuma Rao3831# TODO(developer): Set your name3832# Copyright Β© 2023 <your name>38333834# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated3835# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation3836# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,3837# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:38383839# The above copyright notice and this permission notice shall be included in all copies or substantial portions of3840# the Software.38413842# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO3843# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL3844# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION3845# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER3846# DEALINGS IN THE SOFTWARE.38473848import time3849import typing3850import asyncio3851import aiohttp3852import bittensor as bt38533854import sybil38553856# import base miner class which takes care of most of the boilerplate3857from sybil.base.miner import BaseMinerNeuron385838593860class Miner(BaseMinerNeuron):3861 """3862 Your miner neuron class. You should use this class to define your miner's behavior. In particular, you should replace the forward function with your own logic. You may also want to override the blacklist and priority functions according to your needs.38633864 This class inherits from the BaseMinerNeuron class, which in turn inherits from BaseNeuron. The BaseNeuron class takes care of routine tasks such as setting up wallet, subtensor, metagraph, logging directory, parsing config, etc. You can override any of the methods in BaseNeuron if you need to customize the behavior.38653866 This class provides reasonable default behavior for a miner such as blacklisting unrecognized hotkeys, prioritizing requests based on stake, and forwarding requests to the forward function. If you need to define custom3867 """38683869 def __init__(self, config=None):3870 super(Miner, self).__init__(config=config)38713872 # TODO(developer): Anything specific to your use case you can do here38733874 async def forward(3875 self, synapse: sybil.protocol.Challenge3876 ) -> sybil.protocol.Challenge:3877 """3878 Processes the incoming 'Challenge' synapse by performing a predefined operation on the input data.3879 This method should be replaced with actual logic relevant to the miner's purpose.38803881 Args:3882 synapse (sybil.protocol.Challenge): The synapse object containing the 'challenge_url' data.3883 """3884 3885 bt.logging.info(f"Received challenge: {synapse.challenge_url}")3886 3887 challenge_url = synapse.challenge_url38883889 try:3890 async with aiohttp.ClientSession() as session:3891 bt.logging.info(f"Sending challenge to {self.miner_server}/challenge")3892 async with session.post(3893 f"{self.miner_server}/challenge",3894 json={"url": challenge_url},3895 headers={"Content-Type": "application/json"},3896 ) as response:3897 response = (await response.json())["response"]3898 synapse.challenge_response = response3899 bt.logging.info(f"Solved challenge: {synapse.challenge_response}")3900 return synapse3901 except Exception as e:3902 bt.logging.error(f"Error solving challenge: {e}")3903 return synapse39043905 async def blacklist(3906 self, synapse: sybil.protocol.Challenge3907 ) -> typing.Tuple[bool, str]:3908 """3909 Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should3910 define the logic for blacklisting requests based on your needs and desired security parameters.39113912 Blacklist runs before the synapse data has been deserialized (i.e. before synapse.data is available).3913 The synapse is instead contracted via the headers of the request. It is important to blacklist3914 requests before they are deserialized to avoid wasting resources on requests that will be ignored.39153916 Args:3917 synapse (sybil.protocol.Dummy): A synapse object constructed from the headers of the incoming request.39183919 Returns:3920 Tuple[bool, str]: A tuple containing a boolean indicating whether the synapse's hotkey is blacklisted,3921 and a string providing the reason for the decision.39223923 This function is a security measure to prevent resource wastage on undesired requests. It should be enhanced3924 to include checks against the metagraph for entity registration, validator status, and sufficient stake3925 before deserialization of synapse data to minimize processing overhead.39263927 Example blacklist logic:3928 - Reject if the hotkey is not a registered entity within the metagraph.3929 - Consider blacklisting entities that are not validators or have insufficient stake.39303931 In practice it would be wise to blacklist requests from entities that are not validators, or do not have3932 enough stake. This can be checked via metagraph.S and metagraph.validator_permit. You can always attain3933 the uid of the sender via a metagraph.hotkeys.index( synapse.dendrite.hotkey ) call.39343935 Otherwise, allow the request to be processed further.3936 """39373938 if synapse.dendrite is None or synapse.dendrite.hotkey is None:3939 bt.logging.warning(3940 "Received a request without a dendrite or hotkey."3941 )3942 return True, "Missing dendrite or hotkey"39433944 # TODO(developer): Define how miners should blacklist requests.3945 uid = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)3946 if (3947 not self.config.blacklist.allow_non_registered3948 and synapse.dendrite.hotkey not in self.metagraph.hotkeys3949 ):3950 # Ignore requests from un-registered entities.3951 bt.logging.trace(3952 f"Blacklisting un-registered hotkey {synapse.dendrite.hotkey}"3953 )3954 return True, "Unrecognized hotkey"39553956 if self.config.blacklist.force_validator_permit:3957 # If the config is set to force validator permit, then we should only allow requests from validators.3958 if not self.metagraph.validator_permit[uid]:3959 bt.logging.warning(3960 f"Blacklisting a request from non-validator hotkey {synapse.dendrite.hotkey}"3961 )3962 return True, "Non-validator hotkey"39633964 bt.logging.trace(3965 f"Not Blacklisting recognized hotkey {synapse.dendrite.hotkey}"3966 )3967 return False, "Hotkey recognized!"39683969 async def priority(self, synapse: sybil.protocol.Challenge) -> float:3970 """3971 The priority function determines the order in which requests are handled. More valuable or higher-priority3972 requests are processed before others. You should design your own priority mechanism with care.39733974 This implementation assigns priority to incoming requests based on the calling entity's stake in the metagraph.39753976 Args:3977 synapse (sybil.protocol.Dummy): The synapse object that contains metadata about the incoming request.39783979 Returns:3980 float: A priority score derived from the stake of the calling entity.39813982 Miners may receive messages from multiple entities at once. This function determines which request should be3983 processed first. Higher values indicate that the request should be processed first. Lower values indicate3984 that the request should be processed later.39853986 Example priority logic:3987 - A higher stake results in a higher priority value.3988 """3989 if synapse.dendrite is None or synapse.dendrite.hotkey is None:3990 bt.logging.warning(3991 "Received a request without a dendrite or hotkey."3992 )3993 return 0.039943995 # TODO(developer): Define how miners should prioritize requests.3996 caller_uid = self.metagraph.hotkeys.index(3997 synapse.dendrite.hotkey3998 ) # Get the caller index.3999 priority = float(4000 self.metagraph.S[caller_uid]4001 ) # Return the stake as the priority.4002 bt.logging.trace(4003 f"Prioritizing {synapse.dendrite.hotkey} with value: {priority}"4004 )4005 return priority400640074008# This is the main function, which runs the miner.4009if __name__ == "__main__":4010 with Miner() as miner:4011 while True:4012 bt.logging.info(f"Miner running... {time.time()}")4013 time.sleep(20)401440154016---4017target_repo/neurons/validator.py4018---4019# The MIT License (MIT)4020# Copyright Β© 2023 Yuma Rao4021# TODO(developer): Set your name4022# Copyright Β© 2023 <your name>40234024# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated4025# documentation files (the βSoftwareβ), to deal in the Software without restriction, including without limitation4026# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,4027# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:40284029# The above copyright notice and this permission notice shall be included in all copies or substantial portions of4030# the Software.40314032# THE SOFTWARE IS PROVIDED βAS ISβ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO4033# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL4034# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION4035# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER4036# DEALINGS IN THE SOFTWARE.403740384039import os4040import time4041import datetime40424043# Bittensor4044import bittensor as bt4045import wandb40464047# import base validator class which takes care of most of the boilerplate4048from sybil.base.validator import BaseValidatorNeuron40494050# Bittensor Validator Template:4051from sybil.validator import forward405240534054class Validator(BaseValidatorNeuron):4055 """4056 Your validator neuron class. You should use this class to define your validator's behavior. In particular, you should replace the forward function with your own logic.40574058 This class inherits from the BaseValidatorNeuron class, which in turn inherits from BaseNeuron. The BaseNeuron class takes care of routine tasks such as setting up wallet, subtensor, metagraph, logging directory, parsing config, etc. You can override any of the methods in BaseNeuron if you need to customize the behavior.40594060 This class provides reasonable default behavior for a validator such as keeping a moving average of the scores of the miners and using them to set weights at the end of each epoch. Additionally, the scores are reset for new hotkeys at the end of each epoch.4061 """40624063 def __init__(self, config=None):4064 super(Validator, self).__init__(config=config)40654066 bt.logging.info("load_state()")4067 self.load_state()40684069 self.wandb_run_start = None4070 if not self.config.wandb.off:4071 if os.getenv("WANDB_API_KEY"):4072 self.new_wandb_run()4073 else:4074 bt.logging.exception(4075 "WANDB_API_KEY not found. Set it with `export WANDB_API_KEY=<your API key>`. Alternatively, you can disable W&B with --wandb.off, but it is strongly recommended to run with W&B enabled."4076 )4077 self.config.wandb.off = True4078 else:4079 bt.logging.warning(4080 "Running with --wandb.off. It is strongly recommended to run with W&B enabled."4081 )40824083 def new_wandb_run(self):4084 """Creates a new wandb run to save information to."""4085 # Create a unique run id for this run.4086 now = datetime.datetime.now()4087 self.wandb_run_start = now4088 run_id = now.strftime("%Y-%m-%d_%H-%M-%S")4089 name = "validator-" + str(self.uid) + "-" + run_id4090 self.wandb_run = wandb.init(4091 name=name,4092 project="tpn-validators",4093 entity="tpn-subnet",4094 config={4095 "uid": self.uid,4096 "hotkey": self.wallet.hotkey.ss58_address,4097 "run_name": run_id,4098 "type": "validator",4099 },4100 allow_val_change=True,4101 anonymous="allow",4102 )41034104 bt.logging.debug(f"Started a new wandb run: {name}")41054106 async def forward(self):4107 """4108 Validator forward pass. Consists of:4109 - Generating the query4110 - Querying the miners4111 - Getting the responses4112 - Rewarding the miners4113 - Updating the scores4114 """4115 # TODO(developer): Rewrite this function based on your protocol definition.4116 return await forward(self)411741184119# The main function parses the configuration and runs the validator.4120if __name__ == "__main__":4121 with Validator() as validator:4122 while True:4123 bt.logging.info(f"Validator running... {time.time()}")4124 time.sleep(5)412541264127---