From 53f2218a48acda750187ae00f35583493ca6f7a0 Mon Sep 17 00:00:00 2001 From: haihp02 Date: Thu, 14 Nov 2024 01:48:19 +0700 Subject: [PATCH 1/4] validator signs all requests before sending them to niche-storage and proxy-client --- image_generation_subnet/protocol.py | 10 ++++++++-- neurons/validator/validator.py | 13 ++++++++----- neurons/validator/validator_proxy.py | 29 ++++++++++++++++++---------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/image_generation_subnet/protocol.py b/image_generation_subnet/protocol.py index 9f40f8e2..6aaee8b4 100644 --- a/image_generation_subnet/protocol.py +++ b/image_generation_subnet/protocol.py @@ -99,7 +99,7 @@ def deserialize_response(self): "response_dict": self.response_dict, } - def store_response(self, storage_url: str, uid, validator_uid): + def store_response(self, storage_url: str, uid, validator_uid, nonce, signature): if self.model_name == "GoJourney": storage_url = storage_url + "/upload-go-journey-item" data = { @@ -128,6 +128,9 @@ def store_response(self, storage_url: str, uid, validator_uid): "pipeline_params": self.pipeline_params, } } + # Add validator 's signature + data["nonce"] = nonce + data["signature"] = signature try: response = requests.post(storage_url, json=data) response.raise_for_status() @@ -301,7 +304,7 @@ def deserialize_response(self): "model_name": self.model_name, } - def store_response(self, storage_url: str, uid, validator_uid): + def store_response(self, storage_url: str, uid, validator_uid, nonce, signature): storage_url = storage_url + "/upload-multimodal-item" minimized_prompt_output: dict = copy.deepcopy(self.prompt_output) minimized_prompt_output['choices'][0].pop("logprobs") @@ -317,6 +320,9 @@ def store_response(self, storage_url: str, uid, validator_uid): "pipeline_params": self.pipeline_params, } } + # Add validator 's signature + data["nonce"] = nonce + data["signature"] = signature try: response = requests.post(storage_url, json=data) response.raise_for_status() diff --git a/neurons/validator/validator.py b/neurons/validator/validator.py index 26927a8d..7874758c 100644 --- a/neurons/validator/validator.py +++ b/neurons/validator/validator.py @@ -576,7 +576,7 @@ def async_query_and_reward( ) store_thread = threading.Thread( target=self.store_miner_output, - args=(self.config.storage_url, responses, uids, self.uid), + args=(self.config.storage_url, responses, uids), daemon=True, ) store_thread.start() @@ -683,16 +683,19 @@ def prepare_challenge(self, uids_should_rewards, model_name, pipeline_type): return synapses, batched_uids_should_rewards def store_miner_output( - self, storage_url, responses: list[bt.Synapse], uids, validator_uid + self, storage_url, responses: list[bt.Synapse], uids ): if not self.config.share_response: return - - for uid, response in enumerate(responses): + nonce = str(time.time_ns()) + # Calculate validator 's signature + message = f"{self.wallet.hotkey.ss58_address}{nonce}" + signature = f"0x{self.wallet.hotkey.sign(message).hex()}" + for uid, response in zip(uids, responses): if not response.is_success: continue try: - response.store_response(storage_url, uid, validator_uid) + response.store_response(storage_url, uid, self.uid, nonce, signature) break except Exception as e: bt.logging.error(f"Error in storing response: {e}") diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index fa14b627..7de3b187 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -1,3 +1,4 @@ +import time from fastapi import FastAPI, HTTPException, Depends from concurrent.futures import ThreadPoolExecutor import uvicorn @@ -16,12 +17,10 @@ from starlette.concurrency import run_in_threadpool import threading +from neurons.validator.validator import Validator class ValidatorProxy: - def __init__( - self, - validator, - ): + def __init__(self, validator: Validator): self.validator = validator self.get_credentials() self.miner_request_counter = {} @@ -41,16 +40,26 @@ def __init__( self.start_server() def get_credentials(self): + postfix = ( + f":{self.validator.config.proxy.port}/validator_proxy" + if self.validator.config.proxy.port + else "" + ) + ss58_address = self.validator.wallet.hotkey.ss58_address + uid = self.validator.uid + nonce = str(time.time_ns()) + # Calculate validator 's signature + message = f"{postfix}{ss58_address}{nonce}" + signature = f"0x{self.validator.wallet.hotkey.sign(message).hex()}" + with httpx.Client(timeout=httpx.Timeout(30)) as client: response = client.post( f"{self.validator.config.proxy.proxy_client_url}/get_credentials", json={ - "postfix": ( - f":{self.validator.config.proxy.port}/validator_proxy" - if self.validator.config.proxy.port - else "" - ), - "uid": self.validator.uid, + "postfix": postfix, + "uid": uid, + "signature": signature, + "nonce": nonce }, ) response.raise_for_status() From 4941afe849036894b029ce2131ebb5740fd2553e Mon Sep 17 00:00:00 2001 From: haihp02 Date: Thu, 14 Nov 2024 04:26:08 +0700 Subject: [PATCH 2/4] sign on data before send to niche-storage for better security, the performance degradation expect to be small since batch size max = 4 --- image_generation_subnet/protocol.py | 16 ++++++++++++++-- neurons/validator/validator.py | 7 ++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/image_generation_subnet/protocol.py b/image_generation_subnet/protocol.py index 6aaee8b4..31ec64aa 100644 --- a/image_generation_subnet/protocol.py +++ b/image_generation_subnet/protocol.py @@ -1,3 +1,5 @@ +import time +import json import bittensor as bt import pydantic from generation_models.utils import base64_to_pil_image @@ -99,7 +101,7 @@ def deserialize_response(self): "response_dict": self.response_dict, } - def store_response(self, storage_url: str, uid, validator_uid, nonce, signature): + def store_response(self, storage_url: str, uid, validator_uid, keypair: bt.Keypair): if self.model_name == "GoJourney": storage_url = storage_url + "/upload-go-journey-item" data = { @@ -128,6 +130,11 @@ def store_response(self, storage_url: str, uid, validator_uid, nonce, signature) "pipeline_params": self.pipeline_params, } } + serialized_data = json.dumps(data, sort_keys=True, separators=(',', ':')) + nonce = str(time.time_ns()) + # Calculate validator 's signature + message = f"{serialized_data}{keypair.ss58_address}{nonce}" + signature = f"0x{keypair.sign(message).hex()}" # Add validator 's signature data["nonce"] = nonce data["signature"] = signature @@ -304,7 +311,7 @@ def deserialize_response(self): "model_name": self.model_name, } - def store_response(self, storage_url: str, uid, validator_uid, nonce, signature): + def store_response(self, storage_url: str, uid, validator_uid, keypair: bt.Keypair): storage_url = storage_url + "/upload-multimodal-item" minimized_prompt_output: dict = copy.deepcopy(self.prompt_output) minimized_prompt_output['choices'][0].pop("logprobs") @@ -320,6 +327,11 @@ def store_response(self, storage_url: str, uid, validator_uid, nonce, signature) "pipeline_params": self.pipeline_params, } } + serialized_data = json.dumps(data, sort_keys=True, separators=(',', ':')) + nonce = str(time.time_ns()) + # Calculate validator 's signature + message = f"{serialized_data}{keypair.ss58_address}{nonce}" + signature = f"0x{keypair.sign(message).hex()}" # Add validator 's signature data["nonce"] = nonce data["signature"] = signature diff --git a/neurons/validator/validator.py b/neurons/validator/validator.py index 7874758c..4015c35a 100644 --- a/neurons/validator/validator.py +++ b/neurons/validator/validator.py @@ -687,15 +687,12 @@ def store_miner_output( ): if not self.config.share_response: return - nonce = str(time.time_ns()) - # Calculate validator 's signature - message = f"{self.wallet.hotkey.ss58_address}{nonce}" - signature = f"0x{self.wallet.hotkey.sign(message).hex()}" + for uid, response in zip(uids, responses): if not response.is_success: continue try: - response.store_response(storage_url, uid, self.uid, nonce, signature) + response.store_response(storage_url, uid, self.uid, self.wallet.hotkey) break except Exception as e: bt.logging.error(f"Error in storing response: {e}") From 22e31f4d8419d3951623860d97d5e86f1900faca Mon Sep 17 00:00:00 2001 From: haihp02 Date: Thu, 14 Nov 2024 16:44:52 +0700 Subject: [PATCH 3/4] fix cylic import error --- neurons/validator/validator_proxy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index 7de3b187..6175d963 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -17,10 +17,8 @@ from starlette.concurrency import run_in_threadpool import threading -from neurons.validator.validator import Validator - class ValidatorProxy: - def __init__(self, validator: Validator): + def __init__(self, validator): self.validator = validator self.get_credentials() self.miner_request_counter = {} From ad96d56aaadc9b11da67bc21aab04ec3a95e74b8 Mon Sep 17 00:00:00 2001 From: haihp02 Date: Fri, 15 Nov 2024 14:27:08 +0700 Subject: [PATCH 4/4] validator now signs store_miner_info requests too --- docs/validator.md | 3 +- .../validator/miner_manager.py | 36 ++++++++++++------- neurons/validator/validator_proxy.py | 2 +- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/docs/validator.md b/docs/validator.md index 69d2093a..0de921ad 100644 --- a/docs/validator.md +++ b/docs/validator.md @@ -30,4 +30,5 @@ If you want to run validation APIs locally, check out [Setup validator endpoint] 3. (Optional) **Enable Auto Update Validator** ``` pm2 start auto_update.sh --name "auto-update" -``` \ No newline at end of file +``` + diff --git a/image_generation_subnet/validator/miner_manager.py b/image_generation_subnet/validator/miner_manager.py index 176ed3f1..c3b84623 100644 --- a/image_generation_subnet/validator/miner_manager.py +++ b/image_generation_subnet/validator/miner_manager.py @@ -1,3 +1,5 @@ +import json +import time import bittensor as bt from image_generation_subnet.protocol import ImageGenerating, Information import torch @@ -150,21 +152,31 @@ def get_model_specific_weights(self, model_name, normalize=True): return model_specific_weights def store_miner_info(self): + catalogue = {} + for k, v in self.validator.nicheimage_catalogue.items(): + catalogue[k] = { + "model_incentive_weight": v.get("model_incentive_weight", 0), + "supporting_pipelines": v.get("supporting_pipelines", []), + } + data = { + "uid": self.validator.uid, + "info": self.all_uids_info, + "version": ig_subnet.__version__, + "catalogue": catalogue, + } + serialized_data = json.dumps(data, sort_keys=True, separators=(',', ':')) + nonce = str(time.time_ns()) + # Calculate validator 's signature + keypair = self.validator.wallet.hotkey + message = f"{serialized_data}{keypair.ss58_address}{nonce}" + signature = f"0x{keypair.sign(message).hex()}" + # Add validator 's signature + data["nonce"] = nonce + data["signature"] = signature try: - catalogue = {} - for k, v in self.validator.nicheimage_catalogue.items(): - catalogue[k] = { - "model_incentive_weight": v.get("model_incentive_weight", 0), - "supporting_pipelines": v.get("supporting_pipelines", []), - } requests.post( self.validator.config.storage_url + "/store_miner_info", - json={ - "uid": self.validator.uid, - "info": self.all_uids_info, - "version": ig_subnet.__version__, - "catalogue": catalogue, - }, + json=data ) self.reset_metadata() except Exception as e: diff --git a/neurons/validator/validator_proxy.py b/neurons/validator/validator_proxy.py index 6175d963..11682658 100644 --- a/neurons/validator/validator_proxy.py +++ b/neurons/validator/validator_proxy.py @@ -18,7 +18,7 @@ import threading class ValidatorProxy: - def __init__(self, validator): + def __init__(self, validator: "neurons.validator.validator.Validator"): self.validator = validator self.get_credentials() self.miner_request_counter = {}