diff --git a/docs/stream_tutorial/client.py b/docs/stream_tutorial/client.py index 67e6f05c..d6cfa2d6 100644 --- a/docs/stream_tutorial/client.py +++ b/docs/stream_tutorial/client.py @@ -29,9 +29,7 @@ async def query_synapse(my_uid, wallet_name, hotkey, network, netuid): wallet = bt.wallet(name=wallet_name, hotkey=hotkey) # instantiate the metagraph with provided network and netuid - metagraph = bt.metagraph( - netuid=netuid, network=network, sync=True, lite=False - ) + metagraph = bt.metagraph(netuid=netuid, network=network, sync=True, lite=False) # Grab the axon you're serving axon = metagraph.axons[my_uid] @@ -40,9 +38,7 @@ async def query_synapse(my_uid, wallet_name, hotkey, network, netuid): dendrite = bt.dendrite(wallet=wallet) async def main(): - responses = await dendrite( - [axon], syn, deserialize=False, streaming=True - ) + responses = await dendrite([axon], syn, deserialize=False, streaming=True) for resp in responses: i = 0 @@ -73,9 +69,7 @@ async def main(): required=True, help="Your unique miner ID on the chain", ) - parser.add_argument( - "--netuid", type=int, required=True, help="Network Unique ID" - ) + parser.add_argument("--netuid", type=int, required=True, help="Network Unique ID") parser.add_argument( "--wallet_name", type=str, default="default", help="Name of the wallet" ) diff --git a/docs/stream_tutorial/config.py b/docs/stream_tutorial/config.py index 7cbe82ca..7507076a 100644 --- a/docs/stream_tutorial/config.py +++ b/docs/stream_tutorial/config.py @@ -37,9 +37,7 @@ def get_config() -> "bt.Config": help="Chain endpoint to connect to.", ) # Adds override arguments for network and netuid. - parser.add_argument( - "--netuid", type=int, default=1, help="The chain subnet uid." - ) + parser.add_argument("--netuid", type=int, default=1, help="The chain subnet uid.") parser.add_argument( "--miner.root", diff --git a/docs/stream_tutorial/miner.py b/docs/stream_tutorial/miner.py index a62814d2..35510e9f 100644 --- a/docs/stream_tutorial/miner.py +++ b/docs/stream_tutorial/miner.py @@ -59,9 +59,7 @@ def __init__(self, config=None, axon=None, wallet=None, subtensor=None): bt.logging.info(f"Running miner on uid: {self.my_subnet_uid}") # The axon handles request processing, allowing validators to send this process requests. - self.axon = axon or bt.axon( - wallet=self.wallet, port=self.config.axon.port - ) + self.axon = axon or bt.axon(wallet=self.wallet, port=self.config.axon.port) # Attach determiners which functions are called when servicing a request. bt.logging.info(f"Attaching forward function to axon.") print(f"Attaching forward function to axon. {self._prompt}") @@ -161,9 +159,7 @@ def run(self): self.axon.serve(netuid=self.config.netuid, subtensor=self.subtensor) # Start starts the miner's axon, making it active on the network. - bt.logging.info( - f"Starting axon server on port: {self.config.axon.port}" - ) + bt.logging.info(f"Starting axon server on port: {self.config.axon.port}") self.axon.start() # --- Run until should_exit = True. @@ -346,9 +342,7 @@ async def _prompt(text: str, send: Send): processing steps or modify how tokens are sent back to the client. """ bt.logging.trace("HI. _PROMPT()") - input_ids = tokenizer( - text, return_tensors="pt" - ).input_ids.squeeze() + input_ids = tokenizer(text, return_tensors="pt").input_ids.squeeze() buffer = [] bt.logging.debug(f"Input text: {text}") bt.logging.debug(f"Input ids: {input_ids}") diff --git a/docs/stream_tutorial/protocol.py b/docs/stream_tutorial/protocol.py index 26e91fdc..25c4e92b 100644 --- a/docs/stream_tutorial/protocol.py +++ b/docs/stream_tutorial/protocol.py @@ -85,9 +85,7 @@ async def process_streaming_response(self, response: StreamingResponse): """ if self.completion is None: self.completion = "" - bt.logging.debug( - "Processing streaming response (StreamingSynapse base class)." - ) + bt.logging.debug("Processing streaming response (StreamingSynapse base class).") async for chunk in response.content.iter_any(): bt.logging.debug(f"Processing chunk: {chunk}") tokens = chunk.decode("utf-8").split("\n") diff --git a/neurons/miner.py b/neurons/miner.py index 5f7b9500..c82736b2 100644 --- a/neurons/miner.py +++ b/neurons/miner.py @@ -147,7 +147,7 @@ async def priority(self, synapse: template.protocol.Dummy) -> float: if synapse.dendrite is None or synapse.dendrite.hotkey is None: bt.logging.warning("Received a request without a dendrite or hotkey.") return 0.0 - + # TODO(developer): Define how miners should prioritize requests. caller_uid = self.metagraph.hotkeys.index( synapse.dendrite.hotkey diff --git a/neurons/validator.py b/neurons/validator.py index e28b972c..0c509b00 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -25,6 +25,7 @@ # import base validator class which takes care of most of the boilerplate from template.base.validator import BaseValidatorNeuron + # Bittensor Validator Template: from template.validator import forward diff --git a/template/api/dummy.py b/template/api/dummy.py index f6a433f1..61b4b519 100644 --- a/template/api/dummy.py +++ b/template/api/dummy.py @@ -33,9 +33,7 @@ def prepare_synapse(self, dummy_input: int) -> Dummy: synapse.dummy_input = dummy_input return synapse - def process_responses( - self, responses: List[Union["bt.Synapse", Any]] - ) -> List[int]: + def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> List[int]: outputs = [] for response in responses: if response.dendrite.status_code != 200: diff --git a/template/api/examples/subnet21.py b/template/api/examples/subnet21.py index 705a3fd2..25b06297 100644 --- a/template/api/examples/subnet21.py +++ b/template/api/examples/subnet21.py @@ -63,17 +63,13 @@ def prepare_synapse( return synapse - def process_responses( - self, responses: List[Union["bt.Synapse", Any]] - ) -> str: + def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> str: success = False failure_modes = {"code": [], "message": []} for response in responses: if response.dendrite.status_code != 200: failure_modes["code"].append(response.dendrite.status_code) - failure_modes["message"].append( - response.dendrite.status_message - ) + failure_modes["message"].append(response.dendrite.status_message) continue stored_cid = ( @@ -107,35 +103,24 @@ def prepare_synapse(self, cid: str) -> RetrieveUser: synapse = RetrieveUser(data_hash=cid) return synapse - def process_responses( - self, responses: List[Union["bt.Synapse", Any]] - ) -> bytes: + def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> bytes: success = False decrypted_data = b"" for response in responses: bt.logging.trace(f"response: {response.dendrite.dict()}") - if ( - response.dendrite.status_code != 200 - or response.encrypted_data is None - ): + if response.dendrite.status_code != 200 or response.encrypted_data is None: continue # Decrypt the response - bt.logging.trace( - f"encrypted_data: {response.encrypted_data[:100]}" - ) + bt.logging.trace(f"encrypted_data: {response.encrypted_data[:100]}") encrypted_data = base64.b64decode(response.encrypted_data) - bt.logging.debug( - f"encryption_payload: {response.encryption_payload}" - ) + bt.logging.debug(f"encryption_payload: {response.encryption_payload}") if ( response.encryption_payload is None or response.encryption_payload == "" or response.encryption_payload == "{}" ): - bt.logging.warning( - "No encryption payload found. Unencrypted data." - ) + bt.logging.warning("No encryption payload found. Unencrypted data.") decrypted_data = encrypted_data else: decrypted_data = decrypt_data_with_private_key( @@ -148,18 +133,14 @@ def process_responses( break if success: - bt.logging.info( - f"Returning retrieved data: {decrypted_data[:100]}" - ) + bt.logging.info(f"Returning retrieved data: {decrypted_data[:100]}") else: bt.logging.error("Failed to retrieve data.") return decrypted_data -async def test_store_and_retrieve( - netuid: int = 22, wallet: "bt.wallet" = None -): +async def test_store_and_retrieve(netuid: int = 22, wallet: "bt.wallet" = None): # Example usage wallet = wallet or bt.wallet() diff --git a/template/api/get_query_axons.py b/template/api/get_query_axons.py index 5d51c8f3..3d1d5470 100644 --- a/template/api/get_query_axons.py +++ b/template/api/get_query_axons.py @@ -62,6 +62,7 @@ async def ping_uids(dendrite, metagraph, uids, timeout=3): bt.logging.debug(f"ping() failed uids : {failed_uids}") return successful_uids, failed_uids + async def get_query_api_nodes(dendrite, metagraph, n=0.1, timeout=3): """ Fetches the available API nodes to query for the particular subnet. @@ -75,13 +76,9 @@ async def get_query_api_nodes(dendrite, metagraph, n=0.1, timeout=3): Returns: list: A list of UIDs representing the available API nodes. """ - bt.logging.debug( - f"Fetching available API nodes for subnet {metagraph.netuid}" - ) + bt.logging.debug(f"Fetching available API nodes for subnet {metagraph.netuid}") vtrust_uids = [ - uid.item() - for uid in metagraph.uids - if metagraph.validator_trust[uid] > 0 + uid.item() for uid in metagraph.uids if metagraph.validator_trust[uid] > 0 ] top_uids = np.where(metagraph.S > np.quantile(metagraph.S, 1 - n))[0].tolist() init_query_uids = set(top_uids).intersection(set(vtrust_uids)) @@ -96,9 +93,7 @@ async def get_query_api_nodes(dendrite, metagraph, n=0.1, timeout=3): return query_uids -async def get_query_api_axons( - wallet, metagraph=None, n=0.1, timeout=3, uids=None -): +async def get_query_api_axons(wallet, metagraph=None, n=0.1, timeout=3, uids=None): """ Retrieves the axons of query API nodes based on their availability and stake. diff --git a/template/base/miner.py b/template/base/miner.py index 1788e24b..1f9f3fa8 100644 --- a/template/base/miner.py +++ b/template/base/miner.py @@ -28,6 +28,7 @@ from typing import Union + class BaseMinerNeuron(BaseNeuron): """ Base class for Bittensor miners. @@ -53,7 +54,10 @@ def __init__(self, config=None): "You are allowing non-registered entities to send requests to your miner. This is a security risk." ) # The axon handles request processing, allowing validators to send this miner requests. - self.axon = bt.axon(wallet=self.wallet, config=self.config() if callable(self.config) else self.config) + self.axon = bt.axon( + wallet=self.wallet, + config=self.config() if callable(self.config) else self.config, + ) # Attach determiners which functions are called when servicing a request. bt.logging.info(f"Attaching forward function to miner axon.") diff --git a/template/base/neuron.py b/template/base/neuron.py index 9b2ce7b2..b7424326 100644 --- a/template/base/neuron.py +++ b/template/base/neuron.py @@ -81,12 +81,8 @@ def __init__(self, config=None): # The wallet holds the cryptographic key pairs for the miner. if self.config.mock: self.wallet = bt.MockWallet(config=self.config) - self.subtensor = MockSubtensor( - self.config.netuid, wallet=self.wallet - ) - self.metagraph = MockMetagraph( - self.config.netuid, subtensor=self.subtensor - ) + self.subtensor = MockSubtensor(self.config.netuid, wallet=self.wallet) + self.metagraph = MockMetagraph(self.config.netuid, subtensor=self.subtensor) else: self.wallet = bt.wallet(config=self.config) self.subtensor = bt.subtensor(config=self.config) @@ -100,9 +96,7 @@ def __init__(self, config=None): self.check_registered() # Each miner gets a unique identity (UID) in the network for differentiation. - self.uid = self.metagraph.hotkeys.index( - self.wallet.hotkey.ss58_address - ) + self.uid = self.metagraph.hotkeys.index(self.wallet.hotkey.ss58_address) bt.logging.info( f"Running neuron on subnet: {self.config.netuid} with uid {self.uid} using network: {self.subtensor.chain_endpoint}" ) @@ -163,10 +157,8 @@ def should_set_weights(self) -> bool: # Define appropriate logic for when set weights. return ( - (self.block - self.metagraph.last_update[self.uid]) - > self.config.neuron.epoch_length - and self.neuron_type != "MinerNeuron" - ) # don't set weights if you're a miner + self.block - self.metagraph.last_update[self.uid] + ) > self.config.neuron.epoch_length and self.neuron_type != "MinerNeuron" # don't set weights if you're a miner def save_state(self): bt.logging.warning( diff --git a/template/base/utils/weight_utils.py b/template/base/utils/weight_utils.py index 26133efd..05d4d171 100644 --- a/template/base/utils/weight_utils.py +++ b/template/base/utils/weight_utils.py @@ -7,9 +7,7 @@ U16_MAX = 65535 -def normalize_max_weight( - x: np.ndarray, limit: float = 0.1 -) -> np.ndarray: +def normalize_max_weight(x: np.ndarray, limit: float = 0.1) -> np.ndarray: r"""Normalizes the numpy array x so that sum(x) = 1 and the max value is not greater than the limit. Args: x (:obj:`np.ndarray`): @@ -44,7 +42,7 @@ def normalize_max_weight( # Determine the cutoff based on the index cutoff_scale = (limit * cumsum[n_values - 1] - epsilon) / ( - 1 - (limit * (len(estimation) - n_values)) + 1 - (limit * (len(estimation) - n_values)) ) cutoff = cutoff_scale * values.sum() @@ -57,7 +55,7 @@ def normalize_max_weight( def convert_weights_and_uids_for_emit( - uids: np.ndarray, weights: np.ndarray + uids: np.ndarray, weights: np.ndarray ) -> Tuple[List[int], List[int]]: r"""Converts weights into integer u32 representation that sum to MAX_INT_WEIGHT. Args: @@ -105,7 +103,9 @@ def convert_weights_and_uids_for_emit( weights = [ float(value) / max_weight for value in weights ] # max-upscale values (max_weight = 1). - bittensor.logging.debug(f"setting on chain max: {max_weight} and weights: {weights}") + bittensor.logging.debug( + f"setting on chain max: {max_weight} and weights: {weights}" + ) weight_vals = [] weight_uids = [] @@ -123,15 +123,26 @@ def convert_weights_and_uids_for_emit( def process_weights_for_netuid( - uids, - weights: np.ndarray, - netuid: int, - subtensor: "bittensor.subtensor", - metagraph: "bittensor.metagraph" = None, - exclude_quantile: int = 0, -) -> Union[tuple[ndarray[Any, dtype[Any]], Union[ - Union[ndarray[Any, dtype[floating[Any]]], ndarray[Any, dtype[complexfloating[Any, Any]]]], Any]], tuple[ - ndarray[Any, dtype[Any]], ndarray], tuple[Any, ndarray]]: + uids, + weights: np.ndarray, + netuid: int, + subtensor: "bittensor.subtensor", + metagraph: "bittensor.metagraph" = None, + exclude_quantile: int = 0, +) -> Union[ + tuple[ + ndarray[Any, dtype[Any]], + Union[ + Union[ + ndarray[Any, dtype[floating[Any]]], + ndarray[Any, dtype[complexfloating[Any, Any]]], + ], + Any, + ], + ], + tuple[ndarray[Any, dtype[Any]], ndarray], + tuple[Any, ndarray], +]: bittensor.logging.debug("process_weights_for_netuid()") bittensor.logging.debug("weights", weights) bittensor.logging.debug("netuid", netuid) @@ -169,14 +180,10 @@ def process_weights_for_netuid( bittensor.logging.warning( "No non-zero weights less then min allowed weight, returning all ones." ) - weights = ( - np.ones(metagraph.n) * 1e-5 - ) # creating minimum even non-zero weights + weights = np.ones(metagraph.n) * 1e-5 # creating minimum even non-zero weights weights[non_zero_weight_idx] += non_zero_weights bittensor.logging.debug("final_weights", weights) - normalized_weights = normalize_max_weight( - x=weights, limit=max_weight_limit - ) + normalized_weights = normalize_max_weight(x=weights, limit=max_weight_limit) return np.arange(len(normalized_weights)), normalized_weights bittensor.logging.debug("non_zero_weights", non_zero_weights) diff --git a/template/base/validator.py b/template/base/validator.py index cf8aadb7..c1ca07ed 100644 --- a/template/base/validator.py +++ b/template/base/validator.py @@ -29,7 +29,10 @@ from traceback import print_exception from template.base.neuron import BaseNeuron -from template.base.utils.weight_utils import process_weights_for_netuid, convert_weights_and_uids_for_emit # TODO: Replace when bittensor switches to numpy +from template.base.utils.weight_utils import ( + process_weights_for_netuid, + convert_weights_and_uids_for_emit, +) # TODO: Replace when bittensor switches to numpy from template.mock import MockDendrite from template.utils.config import add_validator_args @@ -61,9 +64,7 @@ def __init__(self, config=None): # Set up initial scoring weights for validation bt.logging.info("Building validation weights.") - self.scores = np.zeros( - self.metagraph.n, dtype=np.float32 - ) + self.scores = np.zeros(self.metagraph.n, dtype=np.float32) # Init sync with the network. Updates the metagraph. self.sync() @@ -103,15 +104,12 @@ def serve_axon(self): pass except Exception as e: - bt.logging.error( - f"Failed to create Axon initialize with exception: {e}" - ) + bt.logging.error(f"Failed to create Axon initialize with exception: {e}") pass async def concurrent_forward(self): coroutines = [ - self.forward() - for _ in range(self.config.neuron.num_concurrent_forwards) + self.forward() for _ in range(self.config.neuron.num_concurrent_forwards) ] await asyncio.gather(*coroutines) @@ -166,9 +164,7 @@ def run(self): # In case of unforeseen errors, the validator will log the error and continue operations. except Exception as err: bt.logging.error(f"Error during validation: {str(err)}") - bt.logging.debug( - str(print_exception(type(err), err, err.__traceback__)) - ) + bt.logging.debug(str(print_exception(type(err), err, err.__traceback__))) def run_in_background_thread(self): """ @@ -337,13 +333,17 @@ def update_scores(self, rewards: np.ndarray, uids: List[int]): # Handle edge case: If either rewards or uids_array is empty. if rewards.size == 0 or uids_array.size == 0: bt.logging.info(f"rewards: {rewards}, uids_array: {uids_array}") - bt.logging.warning("Either rewards or uids_array is empty. No updates will be performed.") + bt.logging.warning( + "Either rewards or uids_array is empty. No updates will be performed." + ) return # Check if sizes of rewards and uids_array match. if rewards.size != uids_array.size: - raise ValueError(f"Shape mismatch: rewards array of shape {rewards.shape} " - f"cannot be broadcast to uids array of shape {uids_array.shape}") + raise ValueError( + f"Shape mismatch: rewards array of shape {rewards.shape} " + f"cannot be broadcast to uids array of shape {uids_array.shape}" + ) # Compute forward pass rewards, assumes uids are mutually exclusive. # shape: [ metagraph.n ] @@ -354,9 +354,7 @@ def update_scores(self, rewards: np.ndarray, uids: List[int]): # Update scores with rewards produced by this step. # shape: [ metagraph.n ] alpha: float = self.config.neuron.moving_average_alpha - self.scores: np.ndarray = alpha * scattered_rewards + ( - 1 - alpha - ) * self.scores + self.scores: np.ndarray = alpha * scattered_rewards + (1 - alpha) * self.scores bt.logging.debug(f"Updated moving avg scores: {self.scores}") def save_state(self): diff --git a/template/subnet_links.py b/template/subnet_links.py index 58eb50d3..f90ecd6a 100644 --- a/template/subnet_links.py +++ b/template/subnet_links.py @@ -10,7 +10,8 @@ {"name": "sn5", "url": "https://github.com/unconst/ImageSubnet/"}, {"name": "sn6", "url": ""}, {"name": "sn7", "url": "https://github.com/tensorage/tensorage/"}, - { "name": "sn8", + { + "name": "sn8", "url": "https://github.com/taoshidev/proprietary-trading-network/", }, {"name": "sn9", "url": "https://github.com/unconst/pretrain-subnet/"}, diff --git a/template/utils/config.py b/template/utils/config.py index 99c610e9..36f8cf85 100644 --- a/template/utils/config.py +++ b/template/utils/config.py @@ -22,6 +22,7 @@ import bittensor as bt from .logging import setup_events_logger + def is_cuda_available(): try: output = subprocess.check_output(["nvidia-smi", "-L"], stderr=subprocess.STDOUT) @@ -37,6 +38,7 @@ def is_cuda_available(): pass return "cpu" + def check_config(cls, config: "bt.Config"): r"""Checks/validates the config namespace object.""" bt.logging.check_config(config) diff --git a/template/utils/uids.py b/template/utils/uids.py index e0300402..f25c487d 100644 --- a/template/utils/uids.py +++ b/template/utils/uids.py @@ -26,9 +26,7 @@ def check_uid_availability( return True -def get_random_uids( - self, k: int, exclude: List[int] = None -) -> np.ndarray: +def get_random_uids(self, k: int, exclude: List[int] = None) -> np.ndarray: """Returns k available random uids from the metagraph. Args: k (int): Number of uids to return. diff --git a/template/validator/reward.py b/template/validator/reward.py index 0237a77e..efccbe00 100644 --- a/template/validator/reward.py +++ b/template/validator/reward.py @@ -29,7 +29,9 @@ def reward(query: int, response: int) -> float: Returns: - float: The reward value for the miner. """ - bt.logging.info(f"In rewards, query val: {query}, response val: {response}, rewards val: {1.0 if response == query * 2 else 0}") + bt.logging.info( + f"In rewards, query val: {query}, response val: {response}, rewards val: {1.0 if response == query * 2 else 0}" + ) return 1.0 if response == query * 2 else 0 @@ -50,9 +52,7 @@ def get_rewards( """ # Get all the reward results by iteratively calling your reward() function. # Cast response to int as the reward function expects an int type for response. - + # Remove any None values responses = [response for response in responses if response is not None] - return np.array( - [reward(query, int(response)) for response in responses] - ) + return np.array([reward(query, int(response)) for response in responses]) diff --git a/tests/helpers.py b/tests/helpers.py index 92134465..b6cc5810 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -124,10 +124,7 @@ def get_mock_neuron(**kwargs) -> NeuronInfo: def get_mock_neuron_by_uid(uid: int, **kwargs) -> NeuronInfo: return get_mock_neuron( - uid=uid, - hotkey=_get_mock_hotkey(uid), - coldkey=_get_mock_coldkey(uid), - **kwargs + uid=uid, hotkey=_get_mock_hotkey(uid), coldkey=_get_mock_coldkey(uid), **kwargs ) diff --git a/tests/test_mock.py b/tests/test_mock.py index e8eb9afb..e74fe31e 100644 --- a/tests/test_mock.py +++ b/tests/test_mock.py @@ -26,9 +26,7 @@ def test_mock_subtensor(netuid, n, wallet): for neuron in neurons: assert type(neuron) == bt.NeuronInfo - assert subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=neuron.hotkey - ) + assert subtensor.is_hotkey_registered(netuid=netuid, hotkey_ss58=neuron.hotkey) @pytest.mark.parametrize("n", [16, 32, 64]) @@ -78,17 +76,13 @@ async def run(): responses = asyncio.run(run()) for synapse in responses: assert ( - hasattr(synapse, "dendrite") - and type(synapse.dendrite) == bt.TerminalInfo + hasattr(synapse, "dendrite") and type(synapse.dendrite) == bt.TerminalInfo ) dendrite = synapse.dendrite # check synapse.dendrite has (process_time, status_code, status_message) for field in ("process_time", "status_code", "status_message"): - assert ( - hasattr(dendrite, field) - and getattr(dendrite, field) is not None - ) + assert hasattr(dendrite, field) and getattr(dendrite, field) is not None # check that the dendrite take between min_time and max_time assert min_time <= dendrite.process_time diff --git a/tests/test_template_validator.py b/tests/test_template_validator.py index 48e015a9..0df5322a 100644 --- a/tests/test_template_validator.py +++ b/tests/test_template_validator.py @@ -64,9 +64,7 @@ def test_dummy_responses(self): responses = self.neuron.dendrite.query( # Send the query to miners in the network. - axons=[ - self.neuron.metagraph.axons[uid] for uid in self.miner_uids - ], + axons=[self.neuron.metagraph.axons[uid] for uid in self.miner_uids], # Construct a dummy query. synapse=Dummy(dummy_input=self.neuron.step), # All responses have the deserialize function called on them before returning.